Deploying Next.js on Vessl
Vessl builds Next.js apps with Nixpacks, which handles the full Node.js toolchain out of the box. Both the App Router and Pages Router are supported. This guide covers standard SSR apps, self-hosted output mode, and background workers for things like cron jobs.
Prerequisites
- A server added and provisioned in Vessl
- A Next.js repository on GitHub (any version ≥ 12)
package.jsonat the repository root (or inside the monorepo subdirectory you set as Root Directory)
What Vessl auto-detects
Vessl detects package.json and sets:
| Setting | Auto-filled value |
|---|---|
| Build engine | Nixpacks |
| Port | 3000 |
| Post-deploy command | None (add manually if needed) |
Nixpacks reads your package.json scripts and picks the right install + build flow automatically:
- Install:
npm ci(oryarn install/pnpm installbased on lockfile presence) - Build:
npm run build - Start:
npm run start
Step 1 — Configure next.config.js for standalone output
For production self-hosting, use Next.js's standalone output — it bundles only what your app needs and cuts the image size significantly:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
};
module.exports = nextConfig;
With standalone output, Nixpacks copies standalone/ into the final image and starts it with node server.js instead of the next start CLI. If you're not using standalone, npm run start works fine too.
Step 2 — Create the app
- In your project, click New Application.
- Select your Next.js repository and branch.
- Vessl detects Node.js — the port defaults to
3000. - Click Deploy.
Step 3 — Environment variables
Next.js has two categories of env vars:
- Server-only (no prefix): available at runtime in API routes, Server Components, and
getServerSideProps. Set these in the Variables tab. - Browser-exposed (
NEXT_PUBLIC_prefix): baked into the client bundle at build time. They must be set before you deploy, not just before you start.
NEXT_PUBLIC_* variables are embedded in your JavaScript bundle during npm run build. If you change them after a deploy, you must redeploy for the change to take effect in the browser.
Common variables to set:
| Variable | Example |
|---|---|
NEXTAUTH_URL | https://your-app.vessl.sh |
NEXTAUTH_SECRET | Random 32-character string |
DATABASE_URL | Injected automatically when you attach a DB add-on |
NEXT_PUBLIC_API_URL | https://your-app.vessl.sh/api |
Step 4 — Add a database (optional)
Open the app → Overview → Connected Services → Add to attach PostgreSQL, MySQL, or Redis. Vessl injects:
DATABASE_URL=postgresql://<user>:<pass>@<host>:5432/<db>
DB_HOST=<internal hostname>
DB_PORT=5432
DB_DATABASE=<db name>
DB_USERNAME=<username>
DB_PASSWORD=<password>
If you use Prisma, Vessl auto-detects the prisma/ folder and sets the post-deploy command to:
npx prisma migrate deploy
If you use Drizzle (with a drizzle/ folder), it sets:
npm run db:migrate
You can override these from the Settings → Post-deploy command field.
Step 5 — Monorepo / subdirectory setup
If your Next.js app lives in a subdirectory (e.g. apps/web in a monorepo), set the Root Directory field in the wizard to that path. Vessl runs all build and start commands relative to that directory.
See the Monorepos guide for a full walkthrough.
Step 6 — Background workers
For background jobs, cron tasks, or WebSocket servers that need to run alongside your Next.js app, add a daemon from the Daemons tab. Examples:
- Cron runner:
node scripts/cron.js - WebSocket server:
node server/ws.js - BullMQ worker:
node workers/queue.js
Each daemon runs in its own container with the same environment variables as your web app.
Common issues
App starts but returns a blank page
If you're using output: 'standalone', make sure your start command is node .next/standalone/server.js and the public/ and .next/static/ directories are copied into the image. Nixpacks handles this automatically — but if you have a custom Dockerfile, add:
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
NEXT_PUBLIC_* env vars are empty in the browser
These are baked in at build time. Set them in the Variables tab, then trigger a fresh deploy.
Port 3000 not responding
Make sure your app actually listens on the port Vessl injects via the PORT environment variable. Next.js's next start does this automatically. If you have a custom server (server.js), read process.env.PORT:
const port = process.env.PORT || 3000;
server.listen(port);
Build runs out of memory
Large Next.js apps can exhaust the default Node.js heap during next build. Set this environment variable to increase the limit:
NODE_OPTIONS=--max-old-space-size=4096