Every so often a client emails me with “I already have hosting” — and it turns out to be a $5/month cPanel shared account. Next.js and shared hosting aren’t a natural pair, but it can work. Here’s what actually happens when you try it, and the hidden traps that turn a “quick deployment” into a two-hour debugging session.
Let’s go through the full picture — setup, gotchas, and when to walk away.
1. Why shared hosting fights Next.js
Next.js in production isn’t a static site — it’s a Node.js server process. Shared hosting was designed for PHP: Apache or Nginx serves files, PHP runs on request, done. Running a persistent Node process is a different animal entirely.
Most cPanel hosts now expose a “Node.js App” feature powered by Phusion Passenger. Passenger manages the process lifecycle, so you don’t get a raw shell with pm2. That’s both the solution and the constraint. Passenger works, but it has opinions about how your app starts — and Next.js doesn’t naturally conform to those opinions out of the box.
On top of that, shared hosts often cap memory per process, throttle CPU, and run older Node versions. If you’re on Next.js 14+ with the App Router and server components, that memory cap becomes a real issue during the build step.
2. Prerequisites before you touch cPanel
Before uploading anything, confirm three things with your host:
- Node.js version available. Next.js 14 requires Node 18.17+. Many shared hosts still default to Node 16 or even 14. Check the Node.js App section in cPanel — you can usually switch versions from a dropdown. If the highest available is Node 16, you’re stuck on Next.js 13 at best.
- Memory limit per process. The build step alone can spike to 500–800 MB. If your host enforces a 256 MB process cap, the build will OOM-kill silently or produce a cryptic “JavaScript heap out of memory” error. Build locally instead — this is non-negotiable on shared hosting.
- Persistent processes allowed. Some very cheap hosts kill any process that runs longer than a few seconds. If that’s the case, no amount of Passenger configuration will help.
3. Build locally, upload selectively
Run next build on your local machine. Then upload only what you actually need — uploading node_modules over FTP is a mistake that turns a 10-minute deploy into a 2-hour one with 40,000 file transfers.
What to upload:
.next/— the compiled outputpublic/— static assetspackage.jsonandpackage-lock.jsonnext.config.js(or.mjs)- Any
.env.productionfile (but never commit it to git)
What to leave behind:
node_modules/— install these on the server via cPanel’s npm console.git/— no reason to upload version historysrc/orapp/source directories — not needed in production
Upload via cPanel’s File Manager or FTP. Once the files are on the server, open the Node.js App manager, point it to your directory, and runnpm install --production from the app’s npm console. This installs only production dependencies server-side.
4. Setting up the Node.js app in cPanel
In cPanel, find “Setup Node.js App” (the name varies by host). Create a new application and set the startup file. This is the part that trips people up: Passenger expects a startup file that exports an Express-style app or callslisten() — Next.js’s default next start CLI doesn’t expose that.
You need a small custom server file:
// server.js — Passenger-compatible Next.js startup
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const app = next({ dev: false });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(process.env.PORT || 3000);
});
Point the startup file to server.js in cPanel, set the application URL to your domain, and hit “Create.” Passenger will manage the process from there. Set the NODE_ENV environment variable to production in the cPanel environment variables section — Next.js behaves differently without it.
5. The gotchas nobody mentions
These are the things that cost me (and clients) hours:
- Passenger restarts are slow. Every time you hit “Restart” in cPanel, Passenger cold-starts the Node process. On a throttled shared host, that can take 10–20 seconds during which the site returns a 503. Plan for this on deploys and warn your client.
- The
.nextfolder permissions. After uploading, check that the.next/cachedirectory is writable by the Node process. If it isn’t, Next.js silently falls back to no caching and your page response times double. - API routes work, but have no persistent state. Passenger can spin up multiple instances of your app. Anything you store in a module-level variable across requests is unreliable. Use a real database or Redis — not in-memory maps.
- Image Optimization is off by default on shared hosts.Next.js’s built-in
next/imageoptimization requires sharp, which requires native binaries. Many shared hosts won’t install native modules. Either setunoptimized: truein your image config or use Cloudinary/ imgix as the loader. - Incremental Static Regeneration (ISR) is flaky. ISR writes cached pages to disk. On shared hosting with restrictive file quotas or inode limits, this can silently fail mid-revalidation. If you rely on ISR, test it explicitly — don’t assume it works.
6. What I’d actually do on a client project
Here’s my honest take: I’ve set this up for clients a handful of times, and it works — barely. The friction is high enough that I always have a conversation before I start the work.
If the client already has shared hosting and refuses to change it, I’ll deploy there. But I narrow the scope aggressively: I’ll turn off ISR, skip Image Optimization, use only static or fully server-rendered routes, and document every limitation in writing before sign-off. Shared hosting is fine for a marketing site with a couple of dynamic routes. It is not fine for an e-commerce site, a dashboard with real-time data, or anything that needs reliable Edge middleware.
The honest upsell — if you can call it that — is a $20/month VPS on DigitalOcean or a free Vercel hobby plan. For most small projects, Vercel’s free tier handles everything a shared host does, with zero configuration, proper ISR, Image Optimization, and Edge Functions. The only reason to stay on shared hosting is if the client is locked into a contract they can’t escape.
If you’re building something more complex — App Router with server components, real-time features, custom middleware — shared hosting will actively hurt you. The time you spend working around its constraints is time you should spend on product. I’ve walked clients through this math in development consultations more times than I can count. The hosting decision is often the first thing we sort out.
If you want to see what a production-grade Next.js deployment actually looks like in terms of architecture decisions, the case studies have a few examples worth reading through. The gap between “it works on shared hosting” and “it works under real load” is bigger than most beginners expect.
Still stuck on the cPanel setup? Or trying to figure out whether your project even belongs on shared hosting? Book a short call and we can go through it together — sometimes 20 minutes of clarity saves days of troubleshooting.
