Why process.env
is Undefined in Next.js Client Components & How to Fix It
Preface
Handling environment variables in Next.js can be tricky, especially when working with client-side components. Many developers encounter the frustrating issue of process.env
returning undefined
in the browser, leading to unexpected errors. This post will explain why this happens, how Next.js treats environment variables, and the best practices for securely exposing necessary values while keeping sensitive information protected.
Introduction
Imagine you're building an admin dashboard for a blog management platform called BlogifyCMS. One of your features allows users to create new blog posts by calling an API endpoint. You set up the environment variables like this:
API_URL="https://api.blogifycms.com"
API_KEY="super-secret-key"
You then attempt to use these values in a client-side function:
'use client';
export const createBlog = async (title: string, content: string) => {
const response = await fetch(`${process.env.API_URL}/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({ title, content })
});
return response.json();
};
However, in the browser, process.env.API_URL
and process.env.API_KEY
return undefined
, breaking the API call. What went wrong?
Why This Happens
1. Next.js Environment Variables Are Server-Only by Default
In Next.js, environment variables are only accessible in the server environment unless explicitly marked for client use. This means process.env
variables won’t be available in client-side JavaScript unless they are prefixed with NEXT_PUBLIC_
.
According to the Next.js documentation:
Environment variables that do not have the
NEXT_PUBLIC_
prefix are only available on the server.
So, in our example, since API_URL
and API_KEY
are not prefixed with NEXT_PUBLIC_
, Next.js does not include them in the client-side bundle, leading to undefined
.
2. Security: Preventing Sensitive Data Exposure
This behavior is intentional. If Next.js automatically exposed all environment variables to the client, sensitive credentials (e.g., API keys, database URLs) could be visible to anyone inspecting the JavaScript bundle in DevTools. This could lead to data breaches, API abuse, or financial losses.
How to Fix It
1. Use NEXT_PUBLIC_
for Safe Client-Side Variables
If you need an environment variable to be accessible in the browser and it’s not sensitive, add NEXT_PUBLIC_
to its name in the .env
file:
NEXT_PUBLIC_API_URL="https://api.blogifycms.com"
Then, reference it in the client component:
export const createBlog = async (title: string, content: string) => {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ title, content })
});
return response.json();
};
🔹 Caution: Never expose private API keys or secrets using NEXT_PUBLIC_
, as they will be accessible in the browser.
2. Move API Calls to a Next.js API Route
If your API key is sensitive (which it usually is), handle the API request on the server using Next.js API routes in the App Router (/app/api/
):
Create an API route in Next.js (for the App Router)
// app/api/create-blog/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const { title, content } = await req.json();
const response = await fetch(`${process.env.API_URL}/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({ title, content })
});
const data = await response.json();
return NextResponse.json(data);
}
Call this API route from the client
export const createBlog = async (title: string, content: string) => {
const response = await fetch('/api/create-blog', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content })
});
return response.json();
};
This keeps API_KEY
hidden on the server while allowing the frontend to safely send requests.
3. Use Server Actions or Server Components (App Router)
If using Next.js App Router (Next.js 13+ with /app
directory), use server actions or server components instead of exposing environment variables in the client.
Using a Server Action
'use server';
export async function createBlog(title: string, content: string) {
const response = await fetch(`${process.env.API_URL}/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({ title, content })
});
return response.json();
}
Calling the Server Action from a Client Component
'use client';
import { createBlog } from '../actions';
export default function BlogForm() {
const handleSubmit = async () => {
const response = await createBlog('New Post', 'This is a test post');
console.log(response);
};
return <button onClick={handleSubmit}>Create Blog</button>;
}
This ensures that environment variables stay server-side only.
Best Practices for Next.js Environment Variables
✔ Use NEXT_PUBLIC_
only for non-sensitive client-side values.
✔ Keep sensitive API keys and credentials server-side.
✔ Move API requests with private keys to Next.js API routes or server actions.
✔ Restart the Next.js dev server after modifying .env
variables (they are loaded at build time).
✔ Never commit .env
files to Git—add them to .gitignore
.
Conclusion
Environment variables in Next.js are server-side by default to protect sensitive information. By keeping private keys on the server and using API routes or server actions, you can ensure security while exposing only necessary values. Following these practices will help you build secure, scalable, and maintainable applications in Next.js.
My Technical Skills

AWS

JavaScript

TypeScript

React

Next.js

Cypress

Figma
