Back to Developer's Study Materials

How to Use .env Files in Next.js (NEXT_PUBLIC_ and Server Variables Explained)

Complete Guide to Environment Variables in Next.js (2026)

Definition: What Are .env Files in Next.js?

.env files are plain-text configuration files that store key-value pairs for your application's runtime settings — API keys, database URLs, feature flags, and service endpoints. Next.js has built-in support for loading these files automatically, with no third-party package required.

Next.js reads .env files at build time and makes the values available via process.env. However, not all variables reach the browser — only those explicitly marked with the NEXT_PUBLIC_ prefix are bundled into client-side JavaScript. This is a deliberate security boundary.

Key insight: Next.js statically inlines NEXT_PUBLIC_ values at build time, which means you must rebuild your app whenever these values change. Server-only variables (no prefix) are read at runtime and can be updated without a rebuild.

What: The .env File Hierarchy — All Variants

Next.js loads multiple .env files and merges them with a defined priority order. Understanding this hierarchy prevents unexpected overrides and missing variables.

FileCommitted?Loaded in Dev?Loaded in Prod?Priority
.env.localNo (gitignored)YesYesHighest
.env.development.localNoYesNoHigh
.env.production.localNoNoYesHigh
.env.developmentYesYesNoMedium
.env.productionYesNoYesMedium
.env.testYesNoNoMedium (test only)
.envYesYesYesLowest (base defaults)

.env.local is not loaded during tests. Jest and other test runners use NODE_ENV=test, which intentionally skips .env.local to ensure reproducible test environments. Use .env.test for test-specific variables.

NEXT_PUBLIC_ vs Server-Only Variables

NEXT_PUBLIC_ (Client + Server)
  • • Inlined into browser bundle at build time
  • • Accessible in Client Components
  • • Visible in browser DevTools / source maps
  • • Use for: site URL, analytics IDs, public API endpoints
No prefix (Server Only)
  • • Never included in browser bundle
  • • Only in Server Components, API routes, middleware
  • • Returns undefined in Client Components — silently
  • • Use for: DB URLs, secret API keys, auth secrets

When: When Are .env Files Loaded?

Development (next dev)

.env files are loaded at server startup. Changes to .env files require a server restart — hot reload does NOT pick up new variables. If NEXT_PUBLIC_ vars change, restart the dev server.

Build (next build)

NEXT_PUBLIC_ variables are read and statically replaced at build time. The built output contains literal string values — changing a NEXT_PUBLIC_ variable on Vercel requires a full redeploy.

Production runtime (next start)

Server-side variables (no prefix) are read at runtime from the hosting environment. These can be updated without rebuilding — just restart the server process.

Hosting platforms (Vercel, Railway, Render): Always set environment variables in the hosting dashboard — not in committed .env files. Dashboard vars override all .env files and are injected securely into build and runtime environments.

How To: Use .env Files in Next.js — Step by Step

Step 1: Create .env.local

Create .env.local in your project root (same level as package.json):

# .env.local — NEVER commit this file to git

# Client-side variable (safe to expose — inlined at build time)
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_ANALYTICS_ID=G-XXXXXXXXXX

# Server-only variables (never exposed to browser)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
STRIPE_SECRET_KEY=sk-test-YOUR_KEY_HERE
AUTH_SECRET=your-super-secret-jwt-signing-key-at-least-32-chars

Step 2: Use NEXT_PUBLIC_ in a Client Component

// app/components/Footer.tsx
'use client'

export function Footer() {
  // Works — NEXT_PUBLIC_ is inlined at build time
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL

  return (
    <footer>
      <a href={siteUrl}>Home</a>
      <p>Analytics ID: {process.env.NEXT_PUBLIC_ANALYTICS_ID}</p>
    </footer>
  )
}

Step 3: Use server-only variables in Server Components and API routes

// app/dashboard/page.tsx — Server Component (no 'use client')
import { db } from '@/lib/db'

export default async function DashboardPage() {
  // DATABASE_URL is only available server-side — never reaches browser
  const users = await db.query('SELECT * FROM users LIMIT 10')
  return <main><h1>Dashboard</h1><p>{users.length} users</p></main>
}

// app/api/charge/route.ts
import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-04-10',
})

export async function POST(req: NextRequest) {
  const { amount } = await req.json()
  const paymentIntent = await stripe.paymentIntents.create({
    amount,
    currency: 'usd',
  })
  return NextResponse.json({ clientSecret: paymentIntent.client_secret })
}

Step 4: Add TypeScript types for process.env

Create types/env.d.ts for full autocomplete and typo detection:

// types/env.d.ts
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      // Public (client + server)
      NEXT_PUBLIC_SITE_URL: string
      NEXT_PUBLIC_ANALYTICS_ID: string

      // Server only
      DATABASE_URL: string
      STRIPE_SECRET_KEY: string
      AUTH_SECRET: string
      NODE_ENV: 'development' | 'production' | 'test'
    }
  }
}

// Required to make this a module
export {}
// tsconfig.json — ensure env.d.ts is included
{
  "include": ["next-env.d.ts", "types/**/*.d.ts", "**/*.ts", "**/*.tsx"]
}

Common Mistake: Accessing Server Vars in Client Components

This silently breaks — no error is thrown:

'use client'

export function DatabaseStatus() {
  // BUG: DATABASE_URL has no NEXT_PUBLIC_ prefix
  // This will be undefined in the browser — silently!
  const dbUrl = process.env.DATABASE_URL

  return <p>DB: {dbUrl}</p>  // Renders: "DB: "
}

Fix: if you need a value from the server in a client component, pass it as a prop from a Server Component, or use a server action / API route.

Best Practice: Use .env.local for all local secrets (never commit it), .env for non-secret defaults (commit it), NEXT_PUBLIC_ only for values you intentionally expose to the browser, and your hosting dashboard for all production secrets.

Why: Why NEXT_PUBLIC_ Exists (Security Design)

The NEXT_PUBLIC_ convention enforces a security boundary between your server secrets and the browser. Without this separation, a developer could accidentally expose a database password or secret API key to every visitor — something that has caused real-world data breaches.

By requiring an explicit opt-in prefix, Next.js makes the secure path the default. You cannot accidentally expose a secret — you have to deliberately add the NEXT_PUBLIC_ prefix.

Never expose

DATABASE_URL, STRIPE_SECRET_KEY, AUTH_SECRET, private API keys, JWT signing secrets

Safe to expose (NEXT_PUBLIC_)

Site URL, analytics IDs, Stripe publishable key, public feature flags

Server-side only

Anything talking to a database, payment processor, auth provider, or internal service

Frequently Asked Questions

What is NEXT_PUBLIC_ prefix in Next.js?

NEXT_PUBLIC_ marks an environment variable as safe to expose to the browser. During the build, Next.js inlines the value directly into the JavaScript bundle. Variables without this prefix are only available on the server and are never included in client-side code.

Why is my NEXT_PUBLIC_ variable undefined?

Most common causes: (1) you added the variable after starting the dev server — restart it. (2) The .env file is not in the project root. (3) You forgot to redeploy after adding the variable to Vercel/Railway. (4) The variable name has a typo.

What is the difference between .env and .env.local?

.env is committed to git and holds non-secret defaults. .env.local is gitignored and meant for machine-specific secrets. .env.local always takes priority over .env. Never commit .env.local.

Can I use process.env in client components Next.js?

Yes, but only for NEXT_PUBLIC_ variables. Server-only variables return undefined in client components silently. Always use NEXT_PUBLIC_ for anything a Client Component needs.

How do I add TypeScript types to process.env in Next.js?

Create types/env.d.ts with a NodeJS.ProcessEnv interface declaration. Include the file in your tsconfig.json. This gives full autocomplete and type safety for all env vars without runtime overhead.

Related guides & tools

Share this article with Your Friends, Collegue and Team mates

Stay Updated

Get the latest tool updates, new features, and developer tips delivered to your inbox.

Occasional useful updates only. Unsubscribe in one click — we never sell your email.

Feedback for Next.js .env Files Guide

Tell us what's working, what's broken, or what you wish we built next — it directly shapes our roadmap.

You make the difference

Good feedback is gold — a rough edge you hit today could be smoother for everyone tomorrow.

  • Feature ideas often jump the queue when lots of you ask.
  • Bug reports with steps get fixed faster — paste URLs or examples if you can.
  • Name and email are optional; we won't use them for anything except replying if needed.