Skip to main content

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.json at the repository root (or inside the monorepo subdirectory you set as Root Directory)

What Vessl auto-detects

Vessl detects package.json and sets:

SettingAuto-filled value
Build engineNixpacks
Port3000
Post-deploy commandNone (add manually if needed)

Nixpacks reads your package.json scripts and picks the right install + build flow automatically:

  • Install: npm ci (or yarn install / pnpm install based 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:

next.config.js
/** @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

  1. In your project, click New Application.
  2. Select your Next.js repository and branch.
  3. Vessl detects Node.js — the port defaults to 3000.
  4. 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.
caution

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:

VariableExample
NEXTAUTH_URLhttps://your-app.vessl.sh
NEXTAUTH_SECRETRandom 32-character string
DATABASE_URLInjected automatically when you attach a DB add-on
NEXT_PUBLIC_API_URLhttps://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