Skip to content
Zayne Lovecraft
All posts

Architecture · · 1 min read

Runtime Config in Next.js: Build Once, Deploy Everywhere

How to stop NEXT_PUBLIC_* build-time inlining from forcing per-environment rebuilds, so one immutable artifact deploys across staging, QA, and production markets.

Zayne Lovecraft

Software Engineer III

Close-up of server racks in a data center highlighting modern technology infrastructure.
Photo by panumas nikhomkhai on Pexels

The NEXT_PUBLIC_* build-time trap

Next.js inlines NEXT_PUBLIC_* variables into the client bundle at build time. In a small project, rebuilding per environment is fine. On a platform that deploys to many markets and QA stages, it breaks the "build once, promote everywhere" principle and multiplies CI time.

Inject validated config from the server

The App Router root layout runs on the server, so it can read real container environment variables, validate them, and pass a public subset to the client before hydration.

// src/lib/runtime-config.ts
export interface PublicRuntimeConfig {
  marketRegion: string;
  apiBaseUrl: string;
  mapsApiKey: string;
}

export function getServerRuntimeConfig(): PublicRuntimeConfig {
  return {
    marketRegion: process.env.MARKET_REGION ?? "US",
    apiBaseUrl: process.env.API_BASE_URL ?? "https://api.example.com",
    mapsApiKey: process.env.MAPS_PUBLIC_KEY ?? "",
  };
}

Serialize that object into a context provider (or a small inline script) from the root layout. Client code reads config from the provider instead of process.env, and the same artifact now works in every environment.

Keep middleware, tokens, and client state in sync

Once config is unified, the next source of flakiness is auth. Cache tokens on the server, expose clear session-refresh boundaries, and keep middleware and client state reading from the same source. Silent auth failures go away and promotions between markets become routine.

  • Next.js
  • Architecture
  • CI/CD
  • Docker

Keep reading