4 min readEngineering
Your Next.js app works locally and fails in production: the usual causes
Why a Next.js app that runs fine with npm run dev breaks on Vercel, Docker, AWS or Cloudflare, and how to find which cause it is in minutes.
A Next.js app that works with npm run dev and breaks once deployed is almost always one of a few things: environment variables that aren't set on the host or were baked in at build time, the production build being stricter than dev, a page that became static when you expected it to be live, or code that assumes a long-running server on a platform that doesn't have one. Run npm run build && npm start on your machine first: if it breaks there too, the problem is the build, not the host.
1. Environment variables
- Missing on the host
.env.localisn't deployed. Every variable has to be added in Vercel, your Docker run command or your cloud's settings. - NEXT_PUBLIC_ is baked in at build timeVariables with that prefix are written into the JavaScript when you build. Changing them on the host later does nothing until you rebuild, and in Docker they must be present when the image is built, not just when it runs.
- Secrets in the browserA secret key given the NEXT_PUBLIC_ prefix to make an error go away is now public. Move that call to the server instead.
2. The production build is stricter than dev
next buildfails on type errors that dev only warns about.- File names are case-sensitive on Linux servers:
import Header from "./header"works on a Mac and fails on the host if the file isHeader.tsx. - A different Node version on the host. Pin it in
package.jsonengines or your Dockerfile. - Code that touches
windoworlocalStoragewhile the page is rendered on the server.
3. A page went static, or stayed cached
Pages without dynamic data are rendered once at build time. A page that showed fresh data in dev can show build-day data in production. Look at the build output: pages marked as static were frozen. Reading cookies or headers, export const dynamic = "force-dynamic", or revalidate tells Next.js how fresh a page should be.
4. Serverless and edge limits
- Each request can open a new database connection. Use your database's pooled connection string (Supabase's pooler, for example) or you'll run out of connections under load.
- Functions time out. Long jobs belong in a queue or background task, not in a route handler.
- Edge runtime and Cloudflare Workers don't have every Node API. Libraries that need
fsor native modules fail there. - Files written to disk at runtime disappear between requests.
5. URLs and sign-in callbacks
Hard-coded http://localhost:3000 links, OAuth redirect URLs that only list localhost, Supabase Auth's site URL left on localhost, and images from domains missing in images.remotePatterns. Sign-in that works locally and loops in production is nearly always a callback URL.
6. Docker and AWS specifics
Set output: "standalone" in next.config and copy .next/standalone, .next/static and public into the image; a missing static folder gives you a page with no styles. Listen on 0.0.0.0, not localhost, inside the container, and pass the port your platform expects.
How to find which one it is
- Build locally in production mode
npm run build && npm startwith the production variables. - Read the build log on the hostNot just the last line: the first error is the real one.
- Check the runtime logsA 500 page hides the error; your host's function or container logs show it.
- Compare the variablesList what's set locally and what's set on the host, side by side.
This site runs Next.js on Cloudflare Workers through OpenNext, deployed from GitHub Actions, so I've met most of these first-hand.
Questions people ask
- Why does my Next.js app work in dev but not after build?
- The production build type-checks, renders some pages ahead of time and runs without dev's helpful fallbacks. Run npm run build && npm start locally; the first error in the build log is usually the cause.
- Why are my environment variables undefined in production?
- They're set in .env.local but not on the host, or they have the NEXT_PUBLIC_ prefix and were missing when the app was built. Add them on the host and rebuild.
- Why does my Next.js page show old data in production?
- It was rendered as a static page at build time. Make it dynamic, or set a revalidate time so it refreshes.
- Do I need Docker to deploy Next.js?
- No. Vercel, Netlify and Cloudflare deploy it without Docker. Docker helps when you run it on your own servers, AWS ECS or similar, and then output: standalone keeps the image small.