# Node.js apps: Next.js, Nuxt, SvelteKit and Express

> Build command, start command, the PORT we hand you, static files and WebSockets.

Source: https://veldhost.eu/docs/node-apps

A **JavaScript app** site runs your own Node.js server: Next.js, Nuxt, SvelteKit, Express, or anything else that listens on a port. We build it from Git, start it as a supervised service and put nginx in front. This article covers what your app must do, and what we do for it.

## How it runs

- **Node.js 22 or 24** (long-term support builds), switchable on the site page under Configure → **Node.js**.
- Your app is started by the system service manager as its own user, restarted if it crashes, and given a memory limit that fits your plan.
- nginx listens on the outside. Anything in your project's `public/` folder is served straight from disk; every other request — pages, API routes, WebSockets — is proxied to your app.
- TLS, your domain, backups and monitoring work exactly as for every other site.

## Deploying

Connect a repository under Publish → **Deploy from Git** (see [Deploy from Git](https://veldhost.eu/docs/deploy-from-git)). On every deploy we:

1. run your **install command** (`npm ci` unless you set one) and your **build command** (`npm run build` when your package.json has a `build` script) in a separate build container;
2. copy the built project — without `node_modules` — into your site;
3. install production dependencies there with your site's own Node version (`npm ci --omit=dev`);
4. restart your app and wait for it to answer on its port. A deploy only counts as done once it does — a broken start command or a missing dependency fails the deploy and keeps the log for you to read.

## The start command

By default we run `npm start` if your package.json defines it, otherwise `node server.js` or `node index.js`. When your framework needs something else, set it on the site page under Configure → Node.js → **Start command**:

| Framework | Build command | Start command |
| --- | --- | --- |
| Next.js | `npm run build` | `npm start` (the default) |
| Nuxt | `npm run build` | `node .output/server/index.mjs` |
| SvelteKit (adapter-node) | `npm run build` | `node build` |
| Express / plain Node | usually none | `node server.js` (the default) |

The command runs from your project root, with your site's Node version first on the `PATH`.

## The PORT environment variable

Your app **must listen on the port in `process.env.PORT`** (we set it to 3000) and on `127.0.0.1` or all interfaces — nginx connects to it locally. Next.js, Nuxt and SvelteKit's Node adapter read `PORT` by themselves; in Express it is one line:

```
app.listen(process.env.PORT || 3000);
```

We also set `NODE_ENV=production` and a sensible V8 heap size for your plan. Add your own variables — API keys, database URLs — under Configure → **Environment variables**; they are written to the service's environment and your app is restarted, so it sees them on the next request. If your app talks to the managed database, the `DB_*` variables are already there: read them with `process.env.DB_HOST` and friends.

## Behind a proxy

Requests reach your app over plain HTTP from nginx, with the real details in headers: `X-Forwarded-Proto` (`https`), `X-Forwarded-For`, `X-Real-IP` and `X-Forwarded-Host`. Tell your framework to trust them so it builds https:// links and sees the visitor's address — in Express, `app.set('trust proxy', 1)`; Next.js and Nuxt handle it out of the box.

## Static assets and WebSockets

Put files that never change — images, fonts, downloads — in `public/`: nginx serves them without waking your app and adds a week of browser caching. Framework asset routes (`/_next/static`, `/_nuxt`) are served by your app as usual. WebSocket upgrades are passed through, so Socket.IO, live reload or a chat feature need no extra setup.

## When something breaks

The [Logs tab](https://veldhost.eu/docs/site-logs) shows your app's own output (everything it prints to the console) newest first — a failed start is right at the top. Common causes: the app listens on a fixed port instead of `PORT`, a dependency is in `devDependencies` but needed at runtime, or the start command points at a file the build did not produce. Fix, push, deploy again.
