vercel / vercel/next.js

loadEnvConfig silently ignores directory argument in monorepo due to internal cache

Open
#92,040 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
142k
Forks
32.4k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

Link to the code that reproduces this issue

https://github.com/aynaitlamine/loadenvconfig-nextjs-issue-reproduction

To Reproduce
  1. Create a monorepo with this structure:
/my-monorepo
  .env               ← DATABASE_URL=postgres://...
  apps/
    web/             ← Next.js app
      envConfig.ts
      next.config.ts
  1. In apps/web/envConfig.ts:
import { loadEnvConfig } from '@next/env'
import path from 'node:path'

const { loadedEnvFiles, combinedEnv } = loadEnvConfig(
  path.resolve(process.cwd(), '../../') // monorepo root
)

console.log(loadedEnvFiles)           // []
console.log(combinedEnv.DATABASE_URL) // undefined
  1. Import it as the first import in next.config.ts:
import './envConfig.ts'
import type { NextConfig } from 'next'

console.log(process.env.DATABASE_URL) // undefined
  1. Run next devDATABASE_URL is always undefined despite the .env file existing at the resolved path.
Current vs. Expected behavior

Current: loadEnvConfig silently returns loadedEnvFiles: [] and undefined for all env vars, even when the directory and .env file are both valid and exist.

Expected: loadEnvConfig should load the .env file from the provided directory and populate process.env accordingly.

Root cause: Next.js calls loadEnvConfig internally before any user code runs, caching the result in a module-level variable. The cache check is:

if (combinedEnv && !forceReload) {
  return { combinedEnv, parsedEnv, loadedEnvFiles: cachedLoadedEnvFiles }
}

This completely ignores the dir argument — so when user code calls loadEnvConfig with a different directory (the monorepo root), it hits the cache and returns the previous empty result with no warning or error.

Workaround: Pass forceReload: true as the 4th positional argument:

loadEnvConfig(
  path.resolve(process.cwd(), '../../'),
  undefined,
  undefined,
  true // forceReload
)

This is undocumented, unintuitive, and requires passing undefined for two arguments just to reach forceReload.

The fix should be to include dir in the cache key:

// Instead of:
if (combinedEnv && !forceReload) return cached

// Should be:
if (combinedEnv && !forceReload && cachedDir === dir) return cached

Or at minimum, log a warning when the cache is returned for a different directory than originally cached.


Which area(s) are affected:
@next/env, Monorepo support

Which stage(s) are affected:
next dev, next build


Provide environment information
Operating System:
  Platform: linux
  Arch: x64
  Version: #14~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jan 15 15:52:10 UTC 2
  Available memory (MB): 7816
  Available CPU cores: 4
Binaries:
  Node: 24.12.0
  npm: 11.6.2
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 16.2.1 // Latest available version is detected (16.2.1).
  eslint-config-next: N/A
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: N/A
Which area(s) are affected? (Select all that apply)

Not sure

Which stage(s) are affected? (Select all that apply)

next dev (local)

Additional context

This is a common monorepo pattern where a single .env lives at the workspace root rather than inside each app. The issue is completely silent — no error, no warning — making it very hard to debug. Users typically waste time verifying paths, file permissions, and variable names before discovering the forceReload workaround by reading the source code directly.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the @next/env loadEnvConfig entry point and trace the module-level cache check described in the issue, reproducing it with apps/web/envConfig.ts and next.config.ts from the linked example. Verify that calls using different directories no longer return the cached empty result, while the documented forceReload behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
next.js, node.js, typescript
Domain
build-system, developer-experience
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.