Skip to content

Environment and Configuration

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.

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 server
const dbUrl = process.env.DATABASE_URL // server-only, safe
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID // also in the client bundle
.env.local
DATABASE_URL="postgres://user:pass@host:5432/db" # never prefix a secret
NEXT_PUBLIC_ANALYTICS_ID="abcdefghijk" # safe to expose

Because NEXT_PUBLIC_ values are baked in at build time, changing one means you must rebuild — it is not read at runtime in the browser.

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 start

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.

next.config.ts
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 nextConfig
graph TD
  A["process.env.SECRET"] --> B["Server only"]
  C["process.env.NEXT_PUBLIC_ID"] --> B
  C --> D["Inlined into client bundle at build time"]
Where each env var can be read
Where is a plain (unprefixed) env var available?
What does the NEXT_PUBLIC_ prefix do?
Which file should hold real secrets?
Which next.config.ts key allows external image hosts?