jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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.

6 MIN READ

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}:

VarValue {Giá trị}
import.meta.env.MODEthe current mode string {chuỗi mode hiện tại}
import.meta.env.DEVtrue in dev {true khi dev}
import.meta.env.PRODtrue in build {true khi build}
import.meta.env.BASE_URLyour base config {config base của bạn}
import.meta.env.SSRtrue 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)
  • .local files 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) → mode development {→ mode development}
  • vite build → mode production {→ mode production}
  • vite build --mode staging → mode staging (loads .env.staging) {→ mode staging (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 .env files, accessed via import.meta.env {config chuỗi thay đổi theo môi trường, từ file .env, truy cập qua import.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.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.env.local luôn tải}.


Key takeaways {Điểm chính}

  • Read env vars via import.meta.env, not process.env {Đọc env var qua import.meta.env, không phải process.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}.
  • .env files cascade by priority; .local files are git-ignored {File .env xếp tầng theo ưu tiên; file .local được git-ignore}.
  • Modes (--mode staging) switch which .env.[mode] loads; define is for build-time constants {Mode đổi file .env.[mode] nào tải; define cho 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, enforceapply, và tự viết một plugin virtual-module}.