AgentSkillsCN

frontend-environment-variables

当您需要在前端代码中处理环境变量时,可使用此技能。

SKILL.md
--- frontmatter
name: frontend-environment-variables
description: Use when working with environment variables in frontend code

Frontend Environment Variables Pattern

⚠️ CRITICAL: ZERO TOLERANCE FOR DIRECT ENV ACCESS ⚠️

ALL environment variable access MUST go through the ENVs utility. NO EXCEPTIONS.

Centralized, type-safe environment variable access through ENVs.ts utility. NEVER access environment variables directly via import.meta.env.* (including import.meta.env.DEV, import.meta.env.PROD, or import.meta.env.MODE) in TypeScript files.

Philosophy: Single source of truth for ALL environment variables with runtime validation and type safety via jet-env.

When to Use This Skill

  • When you see ANY import.meta.env.* usage in TypeScript files (anti-pattern)
  • When you see import.meta.env.DEV, import.meta.env.PROD, or import.meta.env.MODE (anti-pattern)
  • When you see import.meta.env.VITE_* in TypeScript files (anti-pattern)
  • When you see process.env.* anywhere in frontend code (doesn't work in Vite)
  • When adding new environment variables to the frontend
  • When setting up configuration for external services

The ENVs.ts Pattern

Location: spark/frontend/my-vite-app/src/utils/ENVs/ENVs.ts

typescript
import jetEnv, { str } from "jet-env";

const baseENVs = jetEnv(
    {
        ViteSupabaseUrl: str,
        ViteSupabaseAnonKey: str,
        ViteCdnBaseUrl: str,
    },
    { getValue: (key) => import.meta.env[key] }
);

/**
 * Typesafe environment variables utility.
 * ALL environment variable access MUST go through this utility.
 * NEVER use import.meta.env directly in your code.
 */
const ENVs = {
    ...baseENVs,

    /** Development mode - Use instead of import.meta.env.DEV */
    get isDev(): boolean {
        return import.meta.env.DEV;
    },

    /** Production mode - Use instead of import.meta.env.PROD */
    get isProd(): boolean {
        return import.meta.env.PROD;
    },

    /** Current mode string - Use instead of import.meta.env.MODE */
    get mode(): string {
        return import.meta.env.MODE;
    },
} as const;

export { ENVs };

Pattern:

  • Uses jet-env for runtime validation
  • Schema-based with type validators (str, num, bool)
  • Centralized access point for ALL environment variables (custom + mode checks)
  • TypeScript types automatically inferred from schema
  • Built-in development helpers (isDev, isProd, mode)
  • This is the ONLY file allowed to access import.meta.env directly

Correct Usage

Accessing Environment Variables

typescript
// ✅ CORRECT - Using ENVs utility
import { ENVs } from "@/utils/ENVs/ENVs";

const supabaseUrl = ENVs.ViteSupabaseUrl;
const supabaseKey = ENVs.ViteSupabaseAnonKey;
const cdnUrl = ENVs.ViteCdnBaseUrl;

Development Mode Checks

typescript
// ✅ CORRECT - Using ENVs.isDev
import { ENVs } from "@/utils/ENVs/ENVs";

if (ENVs.isDev) {
    console.debug("[Debug] Development mode only");
}

// ✅ CORRECT - Using ENVs.mode
if (ENVs.mode === "development") {
    console.debug("[Dev] Development mode");
}

// ✅ CORRECT - Production check
if (ENVs.isProd) {
    // Production-only code
}

Available ENVs Properties:

  • ENVs.mode - Current mode string ('development' | 'production')
  • ENVs.isDev - boolean (true when mode === 'development')
  • ENVs.isProd - boolean (true when mode === 'production')

Rule: ALL environment access (including mode checks) goes through ENVs utility.

Adding New Environment Variables

Step-by-step:

  1. Add to .env file with VITE_ prefix

    bash
    VITE_API_URL=https://api.example.com
    VITE_CDN_BASE_URL=https://cdn.example.com
    
  2. Add to baseENVs schema in src/utils/ENVs/ENVs.ts

    typescript
    import jetEnv, { str } from "jet-env";
    
    const baseENVs = jetEnv(
        {
            ViteSupabaseUrl: str,
            ViteSupabaseAnonKey: str,
            ViteCdnBaseUrl: str,
            ViteApiUrl: str, // New variable
        },
        { getValue: (key) => import.meta.env[key] }
    );
    
    const ENVs = {
        ...baseENVs,
        get isDev(): boolean {
            return import.meta.env.DEV;
        },
        get isProd(): boolean {
            return import.meta.env.PROD;
        },
        get mode(): string {
            return import.meta.env.MODE;
        },
    } as const;
    
    export { ENVs };
    
  3. Access via ENVs throughout app

    typescript
    import { ENVs } from "@/utils/ENVs/ENVs";
    
    const apiUrl = ENVs.ViteApiUrl;
    const cdnUrl = ENVs.ViteCdnBaseUrl;
    

Naming Convention:

  • .env file: VITE_API_URL (SCREAMING*SNAKE_CASE with VITE* prefix)
  • ENVs schema: ViteApiUrl (PascalCase)
  • Usage: ENVs.ViteApiUrl

Important: ALL frontend environment variables MUST have VITE_ prefix (Vite requirement for security).

Common Anti-Patterns

❌ Direct import.meta.env.VITE_* Access

typescript
// ❌ FORBIDDEN
const url = import.meta.env.VITE_SUPABASE_URL;

// ✅ CORRECT
import { ENVs } from "@/utils/ENVs/ENVs";
const url = ENVs.ViteSupabaseUrl;

❌ Using import.meta.env.DEV / PROD / MODE

typescript
// ❌ FORBIDDEN - Even Vite built-ins must go through ENVs
if (import.meta.env.DEV) {
    console.log("This is forbidden!");
}

if (import.meta.env.PROD) {
    console.log("This is also forbidden!");
}

// ✅ CORRECT - Use ENVs helpers
import { ENVs } from "@/utils/ENVs/ENVs";

if (ENVs.isDev) {
    console.log("This is correct");
}

if (ENVs.isProd) {
    console.log("This is also correct");
}

❌ Using process.env in Frontend

typescript
// ❌ FORBIDDEN - process.env doesn't work in Vite browser code
if (process.env.NODE_ENV === "development") {
    console.log("This will NOT work correctly");
}

// ✅ CORRECT - Use ENVs.isDev
import { ENVs } from "@/utils/ENVs/ENVs";

if (ENVs.isDev) {
    console.log("This works correctly");
}

Why process.env.NODE_ENV fails:

  • process.env is Node.js-only, not available in browser
  • Vite doesn't expose process.env to frontend code
  • Use ENVs.isDev or ENVs.mode instead

❌ Not Adding to ENVs Schema

typescript
// ❌ WRONG - New variable not added to ENVs.ts
const apiUrl = import.meta.env.VITE_NEW_API_URL; // No type safety

// ✅ CORRECT - First add to ENVs.ts, then use
import { ENVs } from "@/utils/ENVs/ENVs";
const apiUrl = ENVs.ViteNewApiUrl; // Type-safe, validated

Detection Checklist

🚫 Violations to find and fix:

  • ANY import.meta.env.* usage in TypeScript files (except in ENVs.ts itself)
  • import.meta.env.VITE_* in TypeScript files (except in ENVs.ts itself)
  • import.meta.env.DEV in TypeScript files (except in ENVs.ts itself)
  • import.meta.env.PROD in TypeScript files (except in ENVs.ts itself)
  • import.meta.env.MODE in TypeScript files (except in ENVs.ts itself)
  • process.env.NODE_ENV in TypeScript files
  • process.env.* anywhere in frontend code
  • Environment variables used without being in ENVs schema

✅ Correct patterns to enforce:

  • ALL env access goes through ENVs.* (including mode checks)
  • Development checks use ENVs.isDev (NOT import.meta.env.DEV)
  • Production checks use ENVs.isProd (NOT import.meta.env.PROD)
  • Mode checks use ENVs.mode (NOT import.meta.env.MODE)
  • All custom variables in .env have VITE_ prefix
  • All custom variables are defined in ENVs.ts schema
  • ENVs.ts is the ONLY file with direct import.meta.env access

File Exclusions

Files where direct env access is acceptable:

  • vite.config.js - Vite configuration (Node.js environment)
  • src/utils/ENVs/ENVs.ts - The utility file itself
  • Other config files in project root (e.g., .eslintrc.js)

Rule: Only TypeScript/TSX files in src/ directory must use ENVs utility.

ALWAYS Use ENVs Utility

🚫 NO EXCEPTIONS - All env access goes through ENVs:

Development/Production Checks (use ENVs helpers):

  • import.meta.env.DEV ❌ → Use ENVs.isDev
  • import.meta.env.PROD ❌ → Use ENVs.isProd
  • import.meta.env.MODE ❌ → Use ENVs.mode

Custom Variables (use ENVs properties):

  • import.meta.env.VITE_* ❌ → Use ENVs.*

The ONLY exception: The ENVs.ts file itself, which is the single source of truth.

Migration Checklist

When fixing violations:

  1. Identify the environment variable being accessed
  2. Determine the type of access:
    • Development mode check: import.meta.env.DEV → Replace with ENVs.isDev
    • Production mode check: import.meta.env.PROD → Replace with ENVs.isProd
    • Mode string check: import.meta.env.MODE → Replace with ENVs.mode
    • Custom variable: import.meta.env.VITE_* → Replace with ENVs.*
    • process.env: process.env.NODE_ENV → Replace with ENVs.isDev
  3. If custom variable, check if it exists in ENVs.ts schema
  4. If not in schema, add it to baseENVs schema in ENVs.ts first
  5. Replace direct access with ENVs.*
  6. Add import: import { ENVs } from '@/utils/ENVs/ENVs'
  7. Run TypeScript check to verify no errors

References

<!-- Last updated: 2025-11-16 - Enforced strict zero-tolerance policy for all import.meta.env access -->