Deploy a Next.js App Without Production Surprises

deploy Next.js app

A production deployment fails when your app needs a server but your hosting only serves files. It also fails when environment variables, Node.js versions, or image handling differ from local development.

To deploy Next.js app code with confidence, first match the deployment method to how your app runs. A marketing site and a dashboard with authenticated server rendering do not have the same requirements.

Start with the runtime model, then choose the platform.

CHOOSE THE RIGHT NEXT.JS DEPLOYMENT MODEL

Next.js supports Vercel, a Node.js server, Docker containers, static exports, and platform adapters. The official deployment options make one point clear: Node.js and Docker support the full framework. Static export does not.

Your app type controls the decision.

Deployment modelUse it whenMain limitation
VercelYou want managed deployment and full Next.js supportPlatform-specific pricing and workflow
Node.js serverYou run a VPS or managed Node hostYou manage process, security, and scaling
DockerYour team uses containers, Kubernetes, ECS, or Cloud RunYou own image builds and runtime setup
Static exportThe site contains only pre-rendered filesNo server-only Next.js features

Static pages do not need a Node runtime

A documentation site, portfolio, or public product site may work as static files. The pages are generated during the build and served through a CDN, Nginx, Apache, or object storage.

Static export works when every route can be known at build time. It is a good fit for content that changes through redeployments.

Server-rendered pages need a running server

Use Vercel, Node.js, or Docker if your app uses server-side rendering, Route Handlers, Server Actions, middleware, authentication checks, or dynamic data fetched per request.

A build can complete successfully and still be the wrong deployment. A static host cannot run code that requires a live Next.js server.

next build creates the production output. It does not turn a server-dependent application into a static site.

DEPLOY A NEXT.JS APP ON VERCEL

Vercel is the shortest route for most teams. Connect a GitHub, GitLab, or Bitbucket repository, select the project, add required environment variables, then deploy.

Vercel detects Next.js projects and runs the production build. It also provides HTTPS, CDN delivery, preview deployments, serverless execution, and image optimization without separate server setup. Vercel describes its platform as supporting every Next.js feature with zero configuration.

Use preview deployments before production

Each pull request can get its own preview URL. Use that URL to test sign-in flows, API routes, redirects, metadata, and environment-dependent features before merging.

Keep production values separate from preview values. A preview deployment should not write to the production database or send real customer emails.

Set environment variables in the project settings. Then redeploy after changing values that are used during the build.

Check the build output, not only the status badge

A green deployment status means the platform built the app. It does not prove your routes return the correct data.

Test these items after deployment:

  • Open the homepage, dynamic routes, and a known 404 URL.
  • Submit a form or call an API route.
  • Check browser console errors and server logs.
  • Confirm redirects use the production domain.
  • Verify OAuth callback URLs and allowed origins.

This process catches most configuration mistakes before users find them.

RUN NEXT.JS ON A NODE.JS SERVER

A Node.js deployment works well when you already operate a VPS, use a managed application host, or need network controls that a managed platform does not provide.

Your production commands are simple:

npm ci

npm run build

npm run start

The final command runs next start. It starts the built application. Do not run next dev in production. Development mode uses different behavior and adds unnecessary overhead.

Set the start command and port correctly

Most hosts provide a PORT environment variable. Next.js can use it with:

next start -p $PORT

For a fixed port on your own server, use:

next start -p 3000

Put Nginx, Caddy, or your cloud load balancer in front of the Node process. The proxy should terminate TLS and route public traffic to the application port.

Use a process manager such as systemd or PM2 if you run on a virtual machine. The app must restart after a server reboot or a process crash.

Pin the same Node.js version everywhere

Node.js version mismatch is a common build failure. Your laptop may run a newer release than CI or production.

Add an engines field to package.json, then use the same Node.js major version in local development, CI, and the production host. Also commit your lockfile and use npm ci, pnpm install --frozen-lockfile, or the matching immutable install command.

If a dependency fails only in production, compare these records:

  • Node.js version
  • Package manager version
  • Lockfile commit
  • Build command
  • Environment variables available during build

The Next.js self-hosting guide confirms that next start supports self-hosted image optimization without extra configuration.

USE STATIC EXPORT ONLY FOR STATIC APPS

Static export is not a cheaper version of server deployment. It is a different runtime model.

Add this setting to next.config.js:

output: "export"

Then run:

npm run build

Next.js writes the generated site to the out directory. Upload that directory to a static host, object storage bucket, CDN, or a lightweight Nginx container.

Know what static export cannot run

A static export cannot run features that need a server after the build completes. That includes request-time rendering and server endpoints.

Do not select static export if your application depends on:

  • Route Handlers that process live requests
  • Server-rendered pages with per-request data
  • Middleware behavior at request time
  • Server Actions
  • Auth checks that must run before HTML is returned
  • Dynamic routes without a build-time path strategy

Known dynamic paths can still be generated during the build. For example, a product catalog can export product pages if the build can fetch every required product slug.

The Next.js static export documentation lists the current feature limits. Check it before changing output on an existing app.

Handle images before you publish

next/image optimization happens at runtime. A static host has no Next.js image server.

For static export, configure a custom image loader that points to an image CDN, or use standard image tags where that trade-off makes sense. Test every remote image URL in the production build.

A missing loader often appears as broken images after a successful static deployment. The page builds. The image path does not.

PACKAGE THE APP AS A DOCKER CONTAINER

Docker is the right choice when your team deploys to Kubernetes, Amazon ECS, Google Cloud Run, Azure Container Apps, or any infrastructure that runs containers.

Set standalone output in next.config.js:

output: "standalone"

This produces a smaller runtime package with the files Next.js traced as required. The standalone output reference explains how Next.js creates this minimal server build.

Build and run the production image

A normal local validation flow looks like this:

docker build -t company-web .

docker run -p 3000:3000 company-web

Open http://localhost:3000 and test the container before pushing it to a registry.

Use a multi-stage Docker build. Install dependencies in one stage, build in another, then copy only the standalone output and required static assets into the final image. This keeps production images smaller and reduces attack surface.

The standalone server runs with:

node server.js

Copy public files and static assets

Standalone output does not remove your need to ship app assets. Your image needs the public directory and the generated .next/static files in the runtime image.

If those files are missing, the app can start while fonts, JavaScript chunks, CSS, or public images return 404 errors. This is a deployment packaging problem, not a React problem.

Set HOSTNAME=0.0.0.0 when the container must accept traffic outside its own network namespace. Also expose the port your platform expects, usually 3000.

CONFIGURE ENVIRONMENT VARIABLES BY RUNTIME

Environment variables cause more production defects than most teams expect. The issue is not only a missing secret. It is often the wrong variable at the wrong time.

Next.js keeps variables server-only by default. Values prefixed with NEXT_PUBLIC_ are included in browser JavaScript during next build.

Separate public values from secrets

Use NEXT_PUBLIC_ only for values safe to expose, such as a public analytics ID or a public application URL.

Never expose these values with a public prefix:

  • Database connection strings
  • API keys with write access
  • Payment provider secrets
  • OAuth client secrets
  • Internal service tokens

A public variable is not protected because it came from an environment setting. It becomes part of the browser bundle.

Build-time values are not runtime values

A NEXT_PUBLIC_ value is baked into the client bundle at build time. If you build one Docker image with NEXT_PUBLIC_API_URL=https://staging.example.com, then promote that same image to production, the browser code still uses the staging URL.

Use server-side runtime variables for values that change by environment. Read them during dynamic server rendering, Route Handlers, or server-side data access.

This matters for teams that build once and promote the same artifact through staging and production.

FIX FAILED BUILDS AND BROKEN PRODUCTION ROUTES

Treat deployment failures as records, not guesses. Keep the build log, commit SHA, Node.js version, deployment target, and the exact command that failed. That gives your team a repeatable support record.

Build fails before deployment completes

Start with the first real error. Later errors often come from the same missing module, type failure, or environment value.

Check these items in order:

  1. Run npm run build locally with a clean install.
  2. Delete local build output, then rerun the build.
  3. Confirm the lockfile matches the package manager.
  4. Check the production Node.js version against package.json.
  5. Add required build-time variables to CI or the hosting dashboard.
  6. Review case-sensitive file paths, especially when building on Linux.

Do not ignore TypeScript, ESLint, or import errors to force a release. A deployment that starts with a known error becomes harder to support.

The app deploys but returns the wrong result

When a route works locally but fails in production, compare the request path, hostname, headers, environment values, and database access.

Check for these patterns:

  • undefined values in browser code usually mean a missing NEXT_PUBLIC_ prefix or a value added after the build.
  • A 500 error on a server route often points to a missing secret, database network rule, or incorrect runtime configuration.
  • A 404 for a dynamic route may mean the route was exported statically without all required paths.
  • Image errors can mean an unapproved remote host, a missing custom loader, or absent container assets.
  • A route that succeeds on Vercel but fails on a VPS may rely on platform behavior you have not recreated.

Keep a short release log with the deploy date, image tag or commit SHA, changed variables, and rollback point. You need this record when an incident happens after a seemingly unrelated release.

FINAL CHECK BEFORE YOU DEPLOY

The safest way to deploy Next.js app code is to choose the runtime before you choose the host. Static export is for fully pre-rendered sites. Node.js and Docker are for applications that need a server. Vercel is the fastest managed option when you want full framework support.

Build with the production command, pin your Node.js version, store secrets outside the repository, and test the deployed result. A reliable release is a traceable release, not a successful build log alone.

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights