Vite · Part 8 — Env Vars & Modes
Environment variables in Vite: import.meta.env, the .env file cascade and priority, the critical VITE_ prefix that gates what reaches the client, built-in vars, define vs env, and custom modes. With an env & mode resolver.
Environment variables are where a small misunderstanding becomes a leaked secret {Biến môi trường là nơi một hiểu lầm nhỏ thành rò rỉ bí mật}. Vite’s model is simple and safe by default — but only if you understand the VITE_ prefix rule {Mô hình của Vite đơn giản và an toàn mặc định — nhưng chỉ khi bạn hiểu luật tiền tố VITE_}.
Pick a mode to see which .env files load, then study the prefix gate that protects your secrets {Chọn một mode để xem file .env nào tải, rồi nghiên cứu cổng tiền tố bảo vệ bí mật}:
1. import.meta.env {import.meta.env}
Vite exposes env vars on import.meta.env (not process.env — there’s no Node in the browser) {Vite phơi env var trên import.meta.env (không phải process.env — không có Node trong trình duyệt)}:
const apiUrl = import.meta.env.VITE_API_URL;
Built-ins always available {Hằng có sẵn}:
| Var | Value {Giá trị} |
|---|---|
import.meta.env.MODE | the current mode string {chuỗi mode hiện tại} |
import.meta.env.DEV | true in dev {true khi dev} |
import.meta.env.PROD | true in build {true khi build} |
import.meta.env.BASE_URL | your base config {config base của bạn} |
import.meta.env.SSR | true during SSR {true khi SSR} |
2. The VITE_ prefix gate — the security rule {Cổng tiền tố VITE_ — luật bảo mật}
This is the most important rule in the whole part {Đây là luật quan trọng nhất cả phần}: only variables prefixed VITE_ are exposed to client code {chỉ biến có tiền tố VITE_ mới được phơi cho code client}.
# .env
VITE_API_URL=https://api.example.com # ✓ reaches the browser
DB_PASSWORD=super-secret # ✗ stays server-only, never bundled
import.meta.env.VITE_API_URL; // "https://api.example.com"
import.meta.env.DB_PASSWORD; // undefined — by design
Why this matters: your client bundle ships to every user’s browser {Vì sao quan trọng: bundle client của bạn gửi tới trình duyệt mọi người dùng}. Anything in it is public {Mọi thứ trong đó là công khai}. The prefix forces you to opt in each value you expose, so a stray STRIPE_SECRET_KEY in .env never accidentally ends up in your JS {Tiền tố buộc bạn chủ động chọn từng giá trị phơi ra, nên một STRIPE_SECRET_KEY lạc trong .env không bao giờ vô tình lọt vào JS}.
There is no such thing as a “secret” client env var. Anything exposed to the browser is readable by anyone. Keep real secrets on a server/edge function and call it from the client {Không có cái gọi là env var “bí mật” phía client. Mọi thứ phơi cho trình duyệt đều đọc được. Giữ bí mật thật trên server/edge và gọi từ client}.
3. The .env file cascade {Tầng file .env}
Vite loads multiple .env files and merges them by priority {Vite tải nhiều file .env và gộp theo ưu tiên}. From highest to lowest {Từ cao xuống thấp}:
.env.[mode].local ← mode-specific, git-ignored (highest)
.env.[mode] ← mode-specific
.env.local ← all modes, git-ignored
.env ← all modes (lowest)
.localfiles are git-ignored — put machine-specific values and local secrets there {File.localđược git-ignore — để giá trị riêng máy và secret cục bộ ở đó}.- Mode-specific files (
.env.production) only load in that mode {File theo mode (.env.production) chỉ tải trong mode đó}. - Higher-priority files override lower ones on conflict {File ưu tiên cao ghi đè file thấp khi xung đột}.
Commit .env (shared defaults) and .env.example (a template); never commit .env.local or .env.*.local {Commit .env (mặc định chung) và .env.example (mẫu); đừng bao giờ commit .env.local hay .env.*.local}.
4. Modes {Mode}
A mode is a named environment that controls which .env.[mode] files load and the value of import.meta.env.MODE {Một mode là môi trường có tên, điều khiển file .env.[mode] nào tải và giá trị import.meta.env.MODE}:
vite(dev) → modedevelopment{→ modedevelopment}vite build→ modeproduction{→ modeproduction}vite build --mode staging→ modestaging(loads.env.staging) {→ modestaging(tải.env.staging)}
Custom modes let you ship a staging build that points at staging APIs without code changes — just a .env.staging file {Mode tùy chỉnh cho bạn ship bản staging trỏ tới API staging mà không đổi code — chỉ một file .env.staging}.
5. define vs env vars {define vs env var}
Both inject values at build time, but for different things {Cả hai chèn giá trị lúc build, nhưng cho việc khác nhau}:
- env vars — string config that varies per environment, from
.envfiles, accessed viaimport.meta.env{config chuỗi thay đổi theo môi trường, từ file.env, truy cập quaimport.meta.env}. define— a raw compile-time text replacement for any expression, set in config {thay thế văn bản lúc biên dịch cho bất kỳ biểu thức nào, đặt trong config}:
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
},
});
Use define for constants known at build time (version, build flags); use env vars for per-environment config {Dùng define cho hằng biết lúc build (version, cờ build); dùng env var cho config theo môi trường}. Remember define values must be JSON-serializable strings {Nhớ giá trị define phải là chuỗi JSON-serializable}.
6. Exercises {Bài tập}
1. You add API_SECRET=abc123 to .env and read import.meta.env.API_SECRET in a component — it’s undefined. Why, and is that good? {Bạn thêm API_SECRET=abc123 vào .env và đọc import.meta.env.API_SECRET trong component — nó undefined. Vì sao, và tốt không?}
Solution {Lời giải}
It lacks the VITE_ prefix, so Vite doesn’t expose it to client code — which is correct: a secret must never reach the browser bundle anyway {Nó thiếu tiền tố VITE_, nên Vite không phơi cho code client — đúng vậy: secret không bao giờ nên vào bundle trình duyệt}.
2. Both .env and .env.production define VITE_API_URL. During vite build, which wins? {Cả .env và .env.production định nghĩa VITE_API_URL. Khi vite build, cái nào thắng?}
Solution {Lời giải}
.env.production — mode-specific files have higher priority than the base .env {.env.production — file theo mode ưu tiên cao hơn .env cơ bản}.
3. You need a build that targets a staging backend without touching code. How do you set it up? {Bạn cần một bản build nhắm backend staging mà không động code. Thiết lập sao?}
Solution {Lời giải}
Create .env.staging with VITE_API_URL=...staging... and run vite build --mode staging {Tạo .env.staging với VITE_API_URL=...staging... và chạy vite build --mode staging}.
Stretch {Nâng cao}: in the resolver, switch modes and note that mode-specific files only appear for the active mode, while .env and .env.local always load {trong resolver, chuyển mode và để ý file theo mode chỉ hiện cho mode đang chạy, còn .env và .env.local luôn tải}.
Key takeaways {Điểm chính}
- Read env vars via
import.meta.env, notprocess.env{Đọc env var quaimport.meta.env, không phảiprocess.env}. - Only
VITE_-prefixed vars reach the client — the secret guardrail {Chỉ biến tiền tốVITE_tới client — rào chắn bí mật}. - There is no secret client env var — keep real secrets server-side {Không có env var bí mật phía client — giữ bí mật thật ở server}.
.envfiles cascade by priority;.localfiles are git-ignored {File.envxếp tầng theo ưu tiên; file.localđược git-ignore}.- Modes (
--mode staging) switch which.env.[mode]loads;defineis for build-time constants {Mode đổi file.env.[mode]nào tải;definecho hằng lúc build}.
Next up {Tiếp theo}
Part 9 — Plugins: the Rollup-compatible plugin API, the hooks you’ll use, enforce and apply, and writing a virtual-module plugin of your own {Phần 9 — Plugin: API plugin tương thích Rollup, các hook bạn dùng, enforce và apply, và tự viết một plugin virtual-module}.