Environment and Configuration
The idea in one sentence
Section titled “The idea in one sentence”Environment variables are server-only by default, and only those prefixed with NEXT_PUBLIC_ are inlined into the client bundle — so a secret must never carry that prefix.
Server-only versus NEXT_PUBLIC_
Section titled “Server-only versus NEXT_PUBLIC_”A plain env var is available only on the server. Prefix it with NEXT_PUBLIC_ and Next.js inlines its value into the JavaScript sent to the browser at build time. That is perfect for a public analytics id and disastrous for an API secret.
// Server Component or route — both work on the serverconst dbUrl = process.env.DATABASE_URL // server-only, safeconst analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID // also in the client bundleDATABASE_URL="postgres://user:pass@host:5432/db" # never prefix a secretNEXT_PUBLIC_ANALYTICS_ID="abcdefghijk" # safe to exposeBecause NEXT_PUBLIC_ values are baked in at build time, changing one means you must rebuild — it is not read at runtime in the browser.
.env files and load order
Section titled “.env files and load order”Next.js loads several .env files and the first match wins. For production the order is .env.production.local, then .env.local, then .env.production, then .env. Keep secrets in .env.local (gitignored) and commit only safe defaults.
.env # committed defaults, safe values only.env.local # gitignored, real secrets.env.development # loaded during next dev.env.production # loaded during next build / next startnext.config.ts essentials
Section titled “next.config.ts essentials”next.config.ts is where you configure the framework itself. The four you reach for most: images.remotePatterns (allow external image hosts), redirects, rewrites, and headers.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 's3.amazonaws.com', pathname: '/my-bucket/**' }, ], }, async redirects() { return [{ source: '/about', destination: '/', permanent: true }] }, async rewrites() { return [{ source: '/docs/:slug', destination: '/help/:slug' }] }, async headers() { return [ { source: '/(.*)', headers: [{ key: 'X-Frame-Options', value: 'DENY' }], }, ] },}
export default nextConfiggraph TD A["process.env.SECRET"] --> B["Server only"] C["process.env.NEXT_PUBLIC_ID"] --> B C --> D["Inlined into client bundle at build time"]