Skip to the solution
EasyFrontend4 min read

How do environment variables work in Next.js (the NEXT_PUBLIC_ prefix)?

1.0k views

The problem

Question presented to candidate: "How do environment variables work in Next.js? Which ones reach the browser, when are their values fixed, and how do you avoid leaking a secret?"

What a strong answer should cover:

  • Variables load from process.env and .env* files; the first match wins in this order: process.env, .env.$(NODE_ENV).local, .env.local, .env.$(NODE_ENV), .env.
  • Only variables prefixed NEXT_PUBLIC_ are available in client code, and they are inlined into the JavaScript at build time.
  • Server-only variables are read at runtime in dynamically rendered code, but a static page captures their values when it is prerendered at build.
  • A server variable read inside a Client Component is not in the browser (undefined), but its server-rendered HTML can contain it.
  • Keep secrets in server-only modules; for per-environment public values without rebuilding, read them on the server and pass them down.

Clarifying questions expected:

  • "Is the same build promoted across environments?" β€” then NEXT_PUBLIC_ values are frozen at the first build.
  • "Is this value secret?" β€” then it must never be prefixed or used in a Client Component.

Code / implementation expected: Yes β€” a server secret, a public value, and passing a runtime value to the client.

  • nextjs
  • env-vars
  • next-public
  • configuration

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

4 min read Β· 8 sections

Read the code

Server secrets, public values and a runtime value passed down
// .env.local
//   STRIPE_SECRET_KEY=sk_live_...
//   NEXT_PUBLIC_SITE_NAME=Lab Shop
//   PUBLIC_API_URL=https://api.staging.example.com   (not prefixed on purpose)

// lib/stripe.ts
import "server-only";
export const stripeKey = process.env.STRIPE_SECRET_KEY!; // server only

// app/layout.tsx (dynamic Server Component passes a runtime value)
import { connection } from "next/server";
import { ConfigProvider } from "./ConfigProvider"; // a Client Component context
export default async function RootLayout({ children }: { children: React.ReactNode }) {
  await connection();                                   // read env at request time
  return (
    <html lang="en">
      <body>
        <ConfigProvider apiUrl={process.env.PUBLIC_API_URL!}>{children}</ConfigProvider>
      </body>
    </html>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 3 of 129 in Next.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Next.js questions
Up nextEasy

How do metadata files like favicon.ico, icon, opengraph-image and manifest work in Next.js?

Continue