Before You Deploy
Three things apply regardless of how you deploy.
Environment variables
Section titled “Environment variables”Every project has a web/.env.example listing every variable the app needs. A few are browser-facing (their names start with NEXT_PUBLIC_) and get built into the app; the rest are server-side (API URLs, secrets) and are read by the running server. You don’t have to sort them yourself. One routine covers both.
Fill in one file, build with it, then put that file on your server. On your build machine, inside web/:
- Install:
npm ci. This also createsweb/.env.localfrom.env.exampleif it isn’t there yet. - Fill it in: open
web/.env.localand set the real production value for every variable, replacing thelocalhostdefaults. - Build:
npm run build. The build reads.env.local, so the browser-facing values are baked into the app. - Copy it to your server: save
web/.env.localas.envin the deployment folder next toserver.js(each platform guide shows where). The standalone server reads a.envfrom its own folder automatically on startup, so there’s nothing else to wire up.
Both .env.local and .env are gitignored, so secrets are never committed. To change a value later, see Changing an environment variable: a browser-facing value needs a rebuild; a server-side value means editing the server’s .env and restarting.
Docker works a little differently: its image is built in isolation and can’t read your env file, so browser-facing values are passed on the build command (--build-arg) and server-side ones via --env-file, which reads web/.env.local from the host at run time. See the Docker guide.
Reverse proxy
Section titled “Reverse proxy”You need a reverse proxy in front of your app for TLS. The proxy must pass three headers so the app knows what the original request looked like:
| Header | What it tells your app |
|---|---|
Host | The hostname the user typed (e.g. app.example.com). |
X-Forwarded-Proto | Whether the original request was http or https. The app uses this to build correct absolute URLs and decide whether to mark cookies as Secure. |
X-Forwarded-For | The real client IP address. |
Each deployment guide shows how to configure these headers for its platform.
Cookie domains
Section titled “Cookie domains”This matters only when the browser talks to a separate auth host directly: external-provider (SSO) sign-in, or an auth API the browser calls cross-origin. If your sign-in posts to a same-origin path that your app rewrites to the auth API (the common username/password setup), the session cookie is first-party to your app and none of this applies. See Deploying a Separate Auth API to work out which case you’re in.
When the auth service is a separate host the browser reaches directly, two cookie properties both have to be right.
1. Same registrable domain: controls whether the cookie is sent at all. The frontend and the auth endpoint must share the same registrable domain (the part you buy from a registrar). For both app.example.com and auth.example.com, that’s example.com. Browsers only send auth cookies on requests that share this domain. Ports don’t factor into the comparison.
| Frontend | Auth endpoint | Same registrable domain? |
|---|---|---|
app.example.com | auth.example.com | Yes, both example.com |
app.example.com | auth.example.net | No, login will fail |
localhost:3000 | localhost:10010 | Yes, ports don’t affect same-site |
If you can’t put both services under one domain, use the reverse proxy to expose the auth endpoint under a path on your main domain (e.g. https://app.example.com/auth/... proxying through to the real auth service). The browser then only ever sees one domain.
2. The Domain attribute: controls which hosts receive the cookie. Sharing the registrable domain is necessary but not sufficient. When the auth service sets the cookie, its Domain attribute decides which hosts get it back:
- No
Domain(host-only): sent only to the exact host that set it (e.g.auth.example.com). Your app onapp.example.comnever receives it, so its session check sees no cookie and bounces the user back to login. Domain=example.com: sent to all subdomains, so both hosts get it. This is what you want, alongsideSecure,HttpOnly, andSameSite.
This hides in local testing: a host-only cookie set by localhost is still sent to localhost on any port, so a missing Domain works locally and only breaks once the two are on different subdomains in production. Make the cookie’s Domain an environment setting: unset locally, the shared parent domain in production. (Don’t set Domain=localhost; several browsers reject it and silently drop the cookie.)
If your project doesn’t use authentication, or your sign-in is a same-origin username/password form, skip this section.