People assume Node.js apps have to live on a VPS. They don't — every Rivervo shared hosting plan has the Setup Node.js App tool in cPanel, which spins up a Phusion Passenger process for your app behind the same Apache front-end that serves your other sites.
This guide walks through the whole flow for the two frameworks 90% of customers ask about: Next.js and Nuxt 3. The end state: your app at https://yourdomain.com (or any subdomain), running on a real Node.js process, surviving reboots, restartable with one click.
If you've never deployed a Node app to cPanel before, the part everyone gets stuck on is the startup file — Passenger needs a single entry script that boots your framework. We'll provide one for each framework below; copy-paste, no edits required.
What you need before starting
- A Rivervo hosting plan (any tier — Node.js Selector is enabled on all plans)
- Node.js v20 or newer locally to build your app
- The built artefact, ready to upload (we'll cover this per framework)
- File Manager or SFTP access to your account (both work)
You do not need root, sudo, systemd, or a reverse-proxy config. Passenger handles all of that.
Step 1 — Build your app locally
You're going to upload a built app, not the source. cPanel will install dependencies but it won't compile React or run a Vite build. Do that on your machine first.
Next.js:
npm ci
npm run buildThat produces .next/ (the built output). Don't next start here — we'll launch it from the startup file in a minute.
Nuxt 3:
npm ci
npm run buildThat produces .output/ (Nuxt's universal build directory). Same deal — don't run anything yet.
Step 2 — Upload the right files
You don't need to upload node_modules/. cPanel's Node.js Selector installs dependencies for you. You also don't need git or build caches.
Upload to your account, ideally to a path outside public_html (e.g. /home/<account>/myapp/). Why outside? Because Passenger will mount public_html at the URL root automatically, and putting your app code in there exposes it.
| Framework | Upload these | Skip |
|---|---|---|
| Next.js | .next/, public/, package.json, package-lock.json, next.config.js, your server.cjs (below) | node_modules/, .git/, .env.local |
| Nuxt 3 | .output/, package.json, package-lock.json, your server.cjs (below) | node_modules/, .nuxt/, .git/, source pages/, components/ |
Two ways to get files there:
- File Manager → Upload → drag and drop a zip → "Extract" is the easiest if you have a flaky connection.
- SFTP (FileZilla, Cyberduck,
rsync) is faster for repeat deployments. Port 22, your cPanel username, your cPanel password.
Step 3 — Add the startup file
This is the file Passenger executes when a request comes in. Drop it next to package.json in your app folder.
For Next.js — server.cjs
const next = require('next')
const http = require('http')
const port = process.env.PORT || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev, dir: __dirname })
const handle = app.getRequestHandler()
app.prepare().then(() => {
http.createServer((req, res) => handle(req, res)).listen(port, () => {
console.log(`Next.js server listening on port ${port}`)
})
})What it does:
- Loads Next, points it at the current directory (where your
.next/andpackage.jsonlive). - Builds a request handler from Next's internal router.
- Starts an HTTP server on the port Passenger assigns (
process.env.PORT). - Falls back to
3000for local testing.
dev is auto-derived from NODE_ENV — set NODE_ENV=production in cPanel (we'll do that in Step 4) and Next runs in production mode.
For Nuxt 3 — server.cjs
import('./.output/server/index.mjs').then(({ createApp }) => {
createApp().then(({ app }) => {
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Nuxt server listening on port ${port}`)
})
})
})What it does:
- Dynamically imports Nuxt's
.output/server/index.mjs(the entry produced bynpm run build). - Uses
createApp()to build the H3 server instance. - Listens on Passenger's assigned port.
The dynamic import() is intentional — .mjs modules can't be require()'d, but a .cjs file can dynamically import them. This is the cleanest pattern that works inside cPanel's launcher.
Step 4 — Configure Node.js Selector
Open cPanel → Setup Node.js App → Create Application.
| Field | Value |
|---|---|
| Node.js version | 20.x or newer |
| Application mode | Production |
| Application root | /home/<account>/myapp (where you uploaded the files) |
| Application URL | yourdomain.com (or a subdomain like app.yourdomain.com) |
| Application startup file | server.cjs |
Click Create. cPanel generates a virtual environment under ~/nodevenv/myapp/20/.
Install dependencies
Once the app is created, scroll down to Detected configuration files — you should see your package.json. Click Run NPM Install and watch the log. If it fails:
- Check that
package.jsonis at the application root (not nested). - For Next.js: confirm
nextis independencies, notdevDependencies(production install skips dev deps). - For Nuxt:
nuxtitself should also be a regular dependency.
Add environment variables
Same screen, Environment variables section. At minimum:
NODE_ENV=productionFor Next.js apps that read at runtime (database URLs, API keys), add them here. Don't ship a .env file with secrets — cPanel's env vars are the right place.
Click Restart after adding any env var. Without a restart, the running process keeps the old environment.
Step 5 — Verify
Visit your application URL. You should see your app.
If you see a Passenger error page, expand it and look at the bottom — there's almost always a clear stack trace. The two most common:
Error: Cannot find module 'next' — npm install didn't run, or it ran in the wrong folder. Re-run NPM Install from cPanel.
Error: ENOENT: no such file or directory, open '.next/...' — you uploaded the source but not the built .next/ directory. Build locally and re-upload.
How to deploy updates
Repeat steps 1, 2, and the Restart click in Node.js Selector. Specifically:
1. Build locally npm run build
2. Upload .next/ or .output/, plus any changed source/config
3. cPanel Restart applicationIf you change package.json, re-run Run NPM Install before restarting.
Common gotchas
WebSockets: cPanel's Passenger handles HTTP/1.1 and HTTP/2. WebSocket upgrade works for Next.js and Nuxt's built-in routes. If you're using a separate WebSocket server (ws, socket.io), it shares the same port and starts via the same server.cjs — no extra config.
File uploads / next.config.js body-parser limits: Passenger's default request body limit is 10 MB. If you're uploading large files, set passenger_max_request_size higher via .htaccess in public_html:
PassengerMaxRequestSize 52428800(50 MB in bytes.)
Cron jobs: if your app needs background tasks (newsletter sends, scheduled exports), don't run them inside the web process. Use cPanel's Cron Jobs to invoke a Node script directly:
/home/<account>/nodevenv/myapp/20/bin/node /home/<account>/myapp/scripts/send-newsletter.jsProcess memory: Passenger restarts your app if it exceeds the per-account RAM cap (depends on your plan). For a typical Next.js / Nuxt app, idle is under 100 MB. If you're hitting limits, profile with process.memoryUsage() and look for leaks before assuming you need a bigger plan.
Custom domains and HTTPS: the app URL you set in Node.js Selector is what Passenger binds. SSL is automatic via AutoSSL — no extra config. If you want a wildcard or a custom domain pointing at a subdirectory, set up the domain in Domains first, then in Node.js Selector.
Outdated Node version: if your app needs Node 22 or 23, open a ticket. We have multiple versions available; the dropdown shows what's installed for your plan.
When not to use shared hosting for Node
To be straightforward — shared hosting Node.js works great for portfolio sites, blogs, marketing pages, small SaaS frontends, internal tools. It struggles for:
- High-throughput APIs (>50 req/sec sustained) — you'll feel the per-account CPU cap
- WebSocket-heavy apps with thousands of concurrent connections
- Apps that fork worker processes (queue workers, scrapers) — Passenger's per-account process budget makes this awkward
- Anything needing systemd, Docker, or root access
For those, our VPS is the right move — full root, your own kernel, runs whatever you want. But for most Next.js and Nuxt apps that aren't fielding huge traffic, this guide is all you need.
TL;DR
npm run buildlocally- Upload
.next/(Next) or.output/(Nuxt) +package.json+server.cjs - cPanel → Setup Node.js App → point at the folder, set startup file to
server.cjs - Run NPM Install, set
NODE_ENV=production, Restart
If anything misbehaves, open a ticket — paste the Passenger error page and we'll point at the line.
— Tomas