Environment & deployment
Secrets that stay secret – and the way out into the world
Only variables prefixed with NEXT_PUBLIC_ reach the browser. Everything else stays on the server – which is where it belongs.
Environment variables
DATABASE_URL="postgres://…" # stays on the server
STRIPE_SECRET_KEY="sk_live_…" # stays on the server
NEXT_PUBLIC_SITE_URL="https://example.com" # reaches the browser
NEXT_PUBLIC_PLAUSIBLE_ID="example.com" # reaches the browser The rule for Environment variablesValues from `.env`. Only those starting with `NEXT_PUBLIC_` reach the browser – everything else stays on the server.→ is simple: the NEXT_PUBLIC_ prefix makes a value public. Anything without it is simply undefined in client components.
The value is baked into the JavaScript that anyone can download. A key with that prefix is therefore published – even if it only appears in one seemingly unremarkable file.
.env for everyone, in the repository (harmless values only!)
.env.local your local secrets – belongs in .gitignore
.env.production for the production build
On the server (Vercel, Docker, …) you set them in the dashboard
or as environment variables – not as a file in the image. The server-onlyA tiny package that breaks the build if a file accidentally ends up in the browser. Protection for code holding secrets.→ package fails the build the moment a file holding secrets is imported by a client component. One import "server-only"; at the top of the file rules out a whole class of mistake.
What the build produces
npm run build
.next/ the result
static/ files with a hash in the name, cacheable forever
server/ the server-side part
npm run start starts the Node server on port 3000 Important: NEXT_PUBLIC_ variables are baked in at build time, not read at start-up. If you use the same image for staging and production you can no longer change them – you need a separate build per environment.
Where to put it
Vercel same team as Next.js, everything without configuration
server actions, ISR, image optimisation just work
Own Node npm run build && npm run start behind a reverse proxy
full control, you handle scaling and caching
Docker output: "standalone" in next.config.js produces a
slim image with only the required dependencies
Static output: "export" – only if you use NO server features.
No ISR, no server actions, no middleware. A purely static export sounds tempting but switches off everything you use Next.js for: server actions, revalidation, image optimisation, middleware. Fine for a documentation site – otherwise rarely the right call.
Before the first deploy
- +
Run
npm run buildlocally and check the symbol column - +
Enter every secret in the target environment – not in the repository
- +
Design an error page and a 404
- +
Add
robots.tsandsitemap.ts
- −
Only ever having tested in development mode
- −
NEXT_PUBLIC_for something that should stay secret - −
Accidentally committing
.env.local
▸ In more depth: Node or edge optional
Server-side code runs in Node by default. For individual routes you can switch to the Edge runtimeA lightweight execution environment close to the user. Quick to start, but can do less than Node.→ – quicker to start, closer to the user, but with fewer capabilities.
export const runtime = "edge"; // in a page.tsx or route.ts
// Edge can: fetch, Web Crypto, streams
// Edge cannot: fs, net, most database drivers Edge pays off for small, frequently called endpoints without a database – redirects, feature flags, simple checks. Anything touching the database stays in Node. Middleware always runs in the edge environment anyway.
Does it stick?
4 questions on this lesson. Wrong answers show up in your stats.