You edit .env.local, restart the dev server, and the old value is still there. The cause is either a variable already set in your shell or a file order that differs from what you assumed.
The rules are not the same in both frameworks
A variable that already exists in your shell beats every .env file in both Next.js and Vite. After that the order diverges.
In Next.js, outside test mode, the order from highest to lowest is .env.[mode].local, .env.local, .env.[mode], .env. In test mode .env.local is skipped.
In Vite the order is .env.[mode].local, .env.[mode], .env.local, .env. The file .env.local sits below the mode specific file, the opposite of Next.js. Vite also accepts any mode name, so vite --mode staging reads .env.staging.
I checked both orders against the loaders themselves, @next/env and Vite's loadEnv, rather than only the documentation. The order was also the subject of a Vite documentation issue in September that asked for it to be clarified.
One command to see which source wins
envwhy answers the question for one key:
npx github:Arthur031221/envwhy API_URL
Run it in the project directory. It finds the framework from package.json, builds the list of sources in priority order, and marks the winner with its file and line. Each shadowed definition is labelled as the same value or a different one, so you can see at a glance whether a stale copy matters. Values stay hidden unless you pass --show-values, which makes the output safe to paste into a bug report.
Add --mode production to see how the answer changes for a build. Run it with no key to list every key that is defined in more than one place.
It checks itself
After building the table, envwhy runs the @next/env or vite package installed in your project in a separate process, loads the same key, and compares. The last line says verified, unverified or skipped. If it says unverified, treat the table as a guess.
Where it stops
It models Next.js and Vite only. It does not evaluate $VAR expansion, it cannot see variables set inside an npm script, and it reads one directory unless you pass --env-dir. It prints a note when it finds a vite.config with envDir or envPrefix, a Bun project, or a script wrapped in dotenv or cross-env, because those can change the result.
The code is MIT licensed with no runtime dependencies: https://github.com/Arthur031221/envwhy
If your layout produces a wrong answer, open an issue with the files and the output.

Top comments (0)