# Why Your Next.js Env Variables Aren't Working in 9.4.4 and How to Configure Them

> Fix Next.js 9.4.4 environment variable issues. Learn how to correctly configure NEXT_PUBLIC_ variables for browser access and understand build-time inlining.

- Repository: [Vercel/next.js](https://github.com/vercel/next.js)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Next.js 9.4.4 requires environment variables to be defined in `.env` files at the project root and uses the `NEXT_PUBLIC_` prefix to expose values to the browser, with variables being inlined at build time rather than read at runtime.**

Next.js 9.4 introduced built-in support for `.env` files through the `@next/env` package, but misconfigurations in file placement or naming conventions often cause nextjs env variables to fail silently. This guide examines the actual implementation in the [`vercel/next.js`](https://github.com/vercel/next.js/blob/main/vercel/next.js) repository to explain why variables disappear and how to properly configure them in version 9.4.4.

## Common Reasons Next.js 9.4.4 Env Variables Fail

### Missing or Misplaced .env Files

The most common cause of `undefined` values is placing `.env` files outside the project root or using incorrect file names. According to the source code in [`packages/next-env/index.ts`](https://github.com/vercel/next.js/blob/main/packages/next-env/index.ts), Next.js only looks for files at the repository root and follows a specific loading order based on the current mode.

### Missing NEXT_PUBLIC_ Prefix for Client Access

Variables without the `NEXT_PUBLIC_` prefix are **server-only** by design. If you try to access `process.env.API_URL` in a browser component, it returns `undefined` because the Next.js bundler only inlines variables starting with `NEXT_PUBLIC_` into the client bundle.

### Build-Time vs Runtime Confusion

Environment variables are **statically inlined** at build time for the client bundle. Changing a variable in your hosting dashboard after running `next build` has no effect on the client-side code. This surprises many developers who expect runtime configuration changes to reflect immediately.

## How Next.js Loads Environment Variables (Source Analysis)

The environment loading logic resides in [`packages/next-env/index.ts`](https://github.com/vercel/next.js/blob/main/packages/next-env/index.ts) and centers around the `loadEnvConfig` function. When the Next.js server starts—whether in development, build, or production mode—it executes the following sequence:

1. **Determines the mode** based on `NODE_ENV` and the `dev` flag (development, test, or production)
2. **Builds the file list** in priority order:
   - `.env.{mode}.local`
   - `.env.local` (skipped in test mode)
   - `.env.{mode}`
   - `.env`
3. **Parses each file** using **dotenv** and **dotenv-expand** to handle variable expansion
4. **Merges values** into `process.env` (only once per process unless `forceReload` is true)

The `NEXT_PUBLIC_` filtering happens later during the webpack build phase. The bundler searches for `process.env.NEXT_PUBLIC_*` references and replaces them with literal string values, ensuring no server secrets leak to the browser.

## Configuring Server-Only and Public Variables

### Server-Only Variables (No Prefix)

Use these for database credentials, API secrets, or any value that should never reach the client. Define them in your `.env` file without any special prefix:

```text

# .env.development

DATABASE_URL=postgres://user:password@localhost:5432/mydb
SECRET_API_KEY=sk_live_1234567890

```

Access them only in server contexts like API routes or `getServerSideProps`:

```typescript
// pages/api/data.ts
export default function handler(req, res) {
  // ✅ Safe to use here - never exposed to browser
  const dbUrl = process.env.DATABASE_URL;
  res.json({ status: 'connected', db: dbUrl?.split('@')[1] });
}

```

### Public Variables (NEXT_PUBLIC_ Prefix)

Prefix any variable needed in the browser with `NEXT_PUBLIC_`:

```text

# .env.production

NEXT_PUBLIC_ANALYTICS_ID=UA-123456-1
NEXT_PUBLIC_API_URL=https://api.example.com

```

Reference them in any component:

```tsx
// components/Analytics.tsx
export default function Analytics() {
  // ✅ Inlined at build time, available in browser
  const trackingId = process.env.NEXT_PUBLIC_ANALYTICS_ID;
  
  return (
    <script
      async
      src={`https://www.google-analytics.com/analytics.js?id=${trackingId}`}
    />
  );
}

```

## Advanced Configuration Patterns

### Using Environment Variables in next.config.js

In Next.js 9.4+, [`next.config.js`](https://github.com/vercel/next.js/blob/main/next.config.js) executes after Next.js automatically loads your `.env` files. You can safely reference `process.env` variables to customize the build:

```javascript
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Access env vars that were loaded from .env files
  env: {
    CUSTOM_BUILD_ID: process.env.BUILD_ID || 'dev',
  },
  // Use server-only vars for redirects/rewrites
  async redirects() {
    return [
      {
        source: '/old-api',
        destination: process.env.NEW_API_ENDPOINT || '/api',
        permanent: true,
      },
    ];
  },
};

export default nextConfig;

```

### Runtime Configuration via API Routes

When values change after build or need to be dynamic, expose them through an API route instead of relying on client-side env vars:

```typescript
// pages/api/config.ts
export default function handler(req, res) {
  // Reads current env at request time, not build time
  res.json({
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    featureFlag: process.env.ENABLE_BETA_FEATURES === 'true',
  });
}

```

```tsx
// components/RuntimeConfig.tsx
import useSWR from 'swr';

export default function RuntimeConfig() {
  const { data, error } = useSWR('/api/config', (url) => 
    fetch(url).then((r) => r.json())
  );
  
  if (error) return <div>Failed to load config</div>;
  if (!data) return <div>Loading...</div>;
  
  return <div>API URL: {data.apiUrl}</div>;
}

```

## Debugging Environment Variable Loading

If variables remain undefined, verify which files Next.js actually loaded using the internal debug command:

```bash

# Shows loaded env files and parsed values

node -e "require('@next/env').loadEnvConfig(process.cwd(), true)"

```

This outputs the `loadedEnvFiles` array containing the `path` and parsed `env` map for each file, confirming whether `.env.development` or `.env.local` was actually read by the loader in [`packages/next-env/index.ts`](https://github.com/vercel/next.js/blob/main/packages/next-env/index.ts).

## Summary

- **Place `.env` files at the project root**: Next.js 9.4.4 only loads files from the repository root following the specific order: `.env.{mode}.local`, `.env.local`, `.env.{mode}`, then `.env`.
- **Use `NEXT_PUBLIC_` for browser access**: Variables without this prefix remain server-only; the bundler inlines only `NEXT_PUBLIC_*` variables into the client bundle.
- **Remember build-time inlining**: Client-side environment variables are frozen at `next build`; changes after building require a rebuild or runtime API exposure.
- **Debug with `@next/env`**: Use `loadEnvConfig` programmatically to verify which files loaded and what values were parsed.

## Frequently Asked Questions

### Why is `process.env.MY_VAR` undefined in my Next.js 9.4.4 API route?

This typically occurs because the `.env` file is not located at the project root or the file name doesn't match the current `NODE_ENV` mode. Next.js looks for `.env.development` when running `next dev` and `.env.production` when running `next start` after a build. Ensure your file is named correctly and placed at the repository root, not in subdirectories like `src/`.

### Why does my environment variable work on the server but return empty on the client?

Next.js 9.4.4 treats environment variables as server-only by default to prevent accidental exposure of secrets. The bundler only inlines variables prefixed with `NEXT_PUBLIC_` into the browser JavaScript bundle. If you reference `process.env.API_KEY` without the prefix in a React component, it evaluates to an empty string because the variable was never sent to the client.

### Can I change environment variables after building my Next.js application?

No, client-side environment variables are statically inlined during `next build` and cannot change without rebuilding. If you need dynamic configuration that changes per deployment or at runtime, expose the values through an API route (e.g., `/api/config`) and fetch them when your application mounts. This approach reads `process.env` at request time rather than build time.

### How do I verify which .env files Next.js 9.4.4 is actually loading?

You can programmatically inspect the loaded configuration by running the internal loader function. Execute `node -e "require('@next/env').loadEnvConfig(process.cwd(), true)"` in your terminal. This outputs the `loadedEnvFiles` array showing exactly which files were parsed and their resolved values, helping you identify if `.env.local` or `.env.development` is being skipped.