All posts
6 min read

Self-Hosting Next.js Without Vercel: What Works, What Breaks, What to Configure

Next.js runs anywhere Node.js does, and on a single server almost everything just works. The surprises: build-time environment variables, caching across instances, streaming behind a proxy, and a few Vercel-only conveniences. A practical guide to running Next.js on your own infrastructure.

deploymentinfrastructureself-hosting

Next.js is made by Vercel, and deploying to Vercel is the smoothest path. But it's an open-source framework, and it runs perfectly well on your own server, a container platform, or any host that runs Node.js. People move off Vercel for predictable pricing, long-running processes, keeping the app next to its database, or simply to have the choice.

On a single server, almost everything works out of the box. This guide covers the setup, and the handful of things that behave differently once Vercel's platform isn't doing them for you.

Checked against the Next.js 16 documentation in September 2026. The official self-hosting guide is the reference for your version.

The basic setup

A Next.js app runs as a Node.js server:

npm ci
npm run build     # next build
npm start         # next start — serves on $PORT (default 3000)

That's genuinely all it takes to run it. Put a reverse proxy in front (Nginx, Caddy, Traefik) to handle HTTPS and your domain, and you're live.

Standalone output for containers

For Docker, enable standalone output in next.config:

const nextConfig = { output: 'standalone' }

next build then produces .next/standalone/ — a minimal server (server.js) plus only the dependencies it needs, far smaller than a full node_modules. Two things to know:

  • Copy the static files yourself. The standalone folder doesn't include public/ or .next/static/. Copy them in (to .next/standalone/public and .next/standalone/.next/static), or serve them from a CDN.
  • Bind to all interfaces. Run it with HOSTNAME=0.0.0.0 so it accepts connections from outside the container, and PORT set to whatever your platform expects.

What just works

On a single Node.js server, these behave the same as on Vercel:

  • Server-side rendering, React Server Components, and Server Actions
  • API routes and route handlers
  • Proxy (called middleware before Next.js 16)
  • Static pages and incremental static regeneration (ISR)
  • Image optimisation (next/image), with no extra configuration under next start
  • Streaming and Suspense

What behaves differently

1. NEXT_PUBLIC_ variables are fixed at build time

Variables prefixed with NEXT_PUBLIC_ are written into the JavaScript bundle during next build. Changing them in your host's settings afterwards does nothing until you rebuild. This bites people who build one Docker image and expect to configure it per environment.

Server-side variables (no prefix) are read at runtime and can change without a rebuild. Where a value needs to differ between environments and be visible in the browser, read it on the server and pass it down, rather than baking it in. (And remember: anything NEXT_PUBLIC_ is public. See How to Keep API Keys Out of an AI-Built App.)

2. Caching lives on the server's disk

Next.js caches rendered pages and fetched data. Self-hosted, that cache is stored on the local filesystem by default. On one server, that's fine. Two consequences:

  • The cache must be writable and should persist. On a host with a temporary filesystem, the cache resets on every deploy — which is harmless but means more rendering.
  • Multiple instances each have their own cache. Run three copies of your app and call revalidatePath on one, and the other two keep serving old content. For multi-instance deployments, configure a shared cache with Next.js's cacheHandler option (Redis is a common backing store).

3. Multiple instances need a few shared settings

Beyond the cache, if you run more than one instance behind a load balancer:

  • Server Actions encryption key. Next.js encrypts data passed to Server Actions with a key generated fresh for each build. Build once and deploy that same build everywhere, or set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY when building so every build uses the same key. Otherwise one instance can't decrypt another's actions, and users see "Failed to find Server Action" errors.
  • Version skew. During a rolling deploy, a browser with the old version's JavaScript may talk to a server running the new one. Setting a deploymentId in next.config lets Next.js detect this and reload cleanly.

On a single instance, none of this matters.

4. Streaming needs the proxy's cooperation

Streaming responses — Suspense boundaries, AI responses streamed token by token — work only if your reverse proxy passes chunks through as they arrive. Nginx buffers responses by default, which makes streaming arrive all at once at the end. Disable buffering for your app (for Nginx, the X-Accel-Buffering: no header or proxy_buffering off).

5. Image optimisation uses your CPU

next/image resizes images on request and caches the results. On Vercel, that's their compute. Self-hosted, it's your server's CPU and memory — a page full of large images can spike both on first load. Give the server headroom, keep the image cache persistent, or use an external image service.

6. Vercel-specific features need replacements

Some things in a Vercel-hosted app aren't Next.js features at all; they're Vercel platform products:

  • Packages starting with @vercel/ — storage, analytics, edge config, and so on.
  • Cron jobs defined in vercel.json — use your host's scheduler or a job library instead (Your App Needs Background Jobs).
  • Preview deployments per branch — a platform feature; see Preview Environments for Every Branch.
  • The Edge runtime. Code marked to run on the Edge runtime still runs self-hosted — on your Node.js server, not at edge locations around the world.

Graceful shutdown and health checks

  • Health checks. Add a route handler like /api/health that returns success only when the app can reach its database, and point your platform's health check at it.
  • Shutdown. On SIGTERM or SIGINT, the Next.js server finishes in-flight requests before exiting. Give it time: the Next.js docs recommend a drain period of 10–30 seconds, so make sure your platform waits that long before force-killing the old instance.

Both are part of deploying without dropping requests — covered in Zero-Downtime Deploys for a Small App.

Where to host it

Anything that runs a long-lived Node.js process works: a VPS, a container platform, a PaaS that runs Docker images, or a managed development platform. The choice is mostly about how much operations work you want — VPS vs PaaS: Where Should a Small App Live? lays out the trade.

A single well-sized server is enough for a surprising amount of traffic, and it sidesteps every multi-instance issue above. Start there; add instances when you actually need them.

The checklist

  • next build and next start work locally with production settings
  • Standalone output for containers, with public/ and .next/static/ copied in
  • App listens on $PORT and 0.0.0.0
  • NEXT_PUBLIC_ variables set at build time; secrets never prefixed
  • Cache directory writable (and shared, if running multiple instances)
  • Same build across instances (or the same build-time encryption key), plus a deploymentId
  • Proxy buffering off, so streaming works
  • @vercel/* packages and vercel.json crons replaced
  • Health check route that tests the database

EasySpawn runs Next.js as a real Node.js server in a persistent workspace — so the cache and uploaded files survive restarts — with managed Postgres, automatic SSL, and your own domain, on flat monthly pricing. See how it works or join the waitlist.

Related: How to Deploy a v0 App · Why Does My App Work Locally but Not in Production? · What Is a Framework? · What Is Next.js? · Reverse Proxies Explained · Content Security Policy

Keep reading