Understanding the Difference Between import.meta.env and process.env
The difference between import.meta.env and process.env is a fundamental part of modern frontend development, but it is also a common source of confusion.
This article compares them from four perspectives: where they come from, where they are used, when their values are determined, and how they affect security.
process.envis the Node.js interface for environment variables, whileimport.meta.envis the interface Vite provides for exposing environment variables to frontend code.
1. What Is process.env?
Where it comes from
process.env is provided by Node.js. It gives Node applications access to environment variables from the current server, local machine, or build environment.
| |
It is commonly used in:
- Node.js services
- build tools such as Webpack, Vite, and Rollup
- server-side rendering
- scripts and backend code
In Node.js, these values can be read from the current process at runtime.
Can it be used in the browser?
Not directly.
Browsers do not provide Node.js’s global process object, so the following code will fail in a native browser environment:
| |
Why does it work in some Webpack projects?
Webpack and similar tools can replace environment variable references during the build.
For example:
| |
may be transformed into:
| |
In this case, the browser is not reading an environment variable at runtime. The build tool has replaced the expression with a fixed value.
2. What Is import.meta.env?
import.meta.env is provided by Vite and is built on top of the standard ES module import.meta syntax.
| |
It does not depend on Node.js’s process object and is designed for modern ESM-based frontend projects.
The distinction is worth noting:
import.metais part of the JavaScript module standard.import.meta.envis a Vite-specific extension.
Its values are available during development and replaced with known values during production builds. Although the application can access them while running, the values themselves are normally determined at build time.
3. Core Differences
| Category | process.env | import.meta.env |
|---|---|---|
| Provided by | Node.js | Vite |
| Main environment | Node.js | Vite applications |
| Available directly in browsers | No | Available through Vite |
| Value source | Process environment | Vite environment files and build configuration |
| Frontend behavior | Usually replaced by a build tool | Replaced by Vite |
| Recommended for Vite client code | No | Yes |
Neither one allows frontend code to read private server environment variables at runtime.
4. Why Vite Does Not Use process.env by Default
Vite does not inject a browser-side process global by default.
Using process.env directly in client code can therefore result in an error.
This design avoids adding Node.js globals to the browser environment, keeps frontend code closer to native browser behavior, and makes static analysis and tree shaking easier.
For Vite client code, use import.meta.env instead.
5. The Correct Way to Use Environment Variables in Vite
Custom variables must use the VITE_ prefix
By default, Vite only exposes custom environment variables whose names begin with VITE_.
| |
They can then be accessed in application code:
| |
A variable without the VITE_ prefix will not be exposed to client code by default.
Built-in variables
Vite also provides several built-in values:
| |
These values are useful for environment-specific behavior and conditional code.
6. Security
Values exposed through import.meta.env are not private.
They may be included in the JavaScript bundle and can be inspected through browser developer tools.
Do not do this:
| |
Frontend environment variables should only contain public configuration, such as:
- public API URLs
- feature flags
- analytics identifiers
- non-sensitive application settings
Secrets should remain in server-side environment variables:
| |
A simple rule is:
If a value is available to browser code, treat it as public.
7. How to Handle Environment Variables in SSR and Full-Stack Projects
In a Vite-based SSR project, such as a custom Vite SSR application or a framework built around similar concepts, server and client code use different sources.
Server-side code:
| |
Client-side code:
| |
This separation is intentional.
Why are there two systems?
The server and browser run in different environments.
| Location | Runtime | Available configuration |
|---|---|---|
| SSR server | Node.js | process.env |
| Client bundle | Browser | import.meta.env |
A browser cannot securely access private server environment variables.
SSR is not the browser running inside Node.js
A common misunderstanding is that SSR simply runs browser code on the server first.
A more accurate model is:
| |
These two stages have different runtimes, different sources of configuration, and different security boundaries.
How values flow in Vite SSR
On the server:
- values can be read from the Node.js process
- private values remain on the server
- server-only values should not enter the client bundle
In the browser:
- public values are exposed by Vite
- values are included in or referenced by the client bundle
- users can inspect them
SSR does not automatically pass server environment variables to the browser, and it should not do so.
8. Three Common SSR Mistakes
Mistake 1: Using process.env directly in shared code
| |
This may work on the server but fail in the browser or be transformed incorrectly during the build.
A runtime check is sometimes used:
| |
However, a clearer approach is to keep server and client configuration in separate modules. This makes the runtime boundary easier to understand and reduces the chance of exposing private values.
Mistake 2: Reading database variables inside a component
| |
A Vue or React component used in SSR may still be included in client-side code and run again during hydration.
Do not access private server values from code that may reach the browser.
Mistake 3: Treating environment variables as runtime configuration
Client-side Vite variables are normally determined during the build:
| |
If configuration must change without rebuilding the application, use a runtime configuration mechanism instead.
Common options include:
- returning configuration from an API
- injecting configuration into HTML
- loading a JSON configuration file
For example:
| |
or:
| |
Anything sent to the browser is still public, even when it is loaded at runtime.
9. A Practical SSR Architecture
A typical configuration boundary looks like this:
| |
The browser communicates with the server through HTTP. The server then accesses databases, storage, and other private infrastructure.
Private configuration should remain inside that server-side boundary.
10. Nuxt and Runtime Configuration
Nuxt provides its own configuration layer to help maintain the same boundary.
| Configuration | Purpose |
|---|---|
runtimeConfig | Server-only configuration |
runtimeConfig.public | Configuration exposed to the client |
process.env | Server environment variables |
Nuxt is essentially helping the application distinguish between private server configuration and public client configuration.
11. Common Misunderstandings
import.meta.env reads values from the server at runtime
It does not. Client-facing values are normally determined during the Vite build.
import.meta.env can dynamically switch environments
Not by itself. Changing a build-time environment variable usually requires a new build.
Use an API, an injected configuration object, or a runtime JSON file when configuration must change after deployment.
process.env can still be used everywhere in Vite
Not in browser code by default.
It can still be used in Node.js scripts, build configuration, SSR server code, and other server-only modules. Adding a browser polyfill is possible, but it is usually unnecessary and can make the client-server boundary less clear.
Conclusion
The basic rule is straightforward:
| |
Keep private values on the server, and never place secrets in environment variables that are exposed to frontend code.
The most important distinction is not the syntax. It is the runtime in which the code executes and whether the value is allowed to reach the browser.