Environment variables and configuration

Next.js · Practical · 10 min

Leaking a secret into the client bundle is one of the most common and costly Next.js mistakes.

Next.js loads environment variables from .env, .env.local, .env.development, .env.production (and .test variants), in a defined precedence order where .env.local overrides .env. Commit .env defaults if useful, never .env.local with secrets.

The crucial rule: variables are server-only by default. process.env.DATABASE_URL works in Server Components, Server Actions and Route Handlers but is undefined in the browser. Only variables prefixed with `NEXT_PUBLIC_` are exposed to client code, and they are inlined at build time into the JavaScript bundle. So a NEXT_PUBLIC_ value can't change after building without rebuilding, and anything prefixed is public.

To keep server code from being imported into Client Components by accident, add import 'server-only' at the top of modules that use secrets; importing them from client code fails the build.

`next.config.ts` configures the framework with a typed NextConfig object. Commonly used options:

  • images.remotePatterns for remote images,
  • redirects() and rewrites() for URL rules,
  • headers() for security headers,
  • output: 'standalone' for container deployments,
  • cacheComponents, reactCompiler, typedRoutes and experimental flags.

Validate required environment variables at startup (for example with Zod) so misconfiguration fails fast.

Example

# .env.local  (not committed)
DATABASE_URL=postgres://user:secret@localhost/app
NEXT_PUBLIC_ANALYTICS_ID=abc123

// lib/db.ts
import 'server-only'                       // build error if imported by a Client Component
export const dbUrl = process.env.DATABASE_URL!

// app/ui/analytics.tsx
// 'use client'
// console.log(process.env.NEXT_PUBLIC_ANALYTICS_ID)   // 'abc123' (inlined at build)
// console.log(process.env.DATABASE_URL)               // undefined in the browser

// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
  output: 'standalone',
  async redirects() {
    return [{ source: '/old-blog/:slug', destination: '/blog/:slug', permanent: true }]
  },
}
export default nextConfig

Remember

  • Env vars are server-only unless prefixed with NEXT_PUBLIC_
  • NEXT_PUBLIC_ values are inlined at build time and visible to everyone
  • import 'server-only' guards secret-using modules; next.config.ts holds framework settings

Watch out for

  • Prefixing a secret API key with NEXT_PUBLIC_ to make it work in a client component, publishing it in the bundle.

Check yourself

  1. A Client Component reads process.env.STRIPE_SECRET_KEY. What value does it get in the browser?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nextjs.org/docs/app/guides/environment-variables