Переменные окружения внутри конфига

Переменные окружения в Vite используются для управления конфигурацией приложения в зависимости от среды выполнения. Они позволяют:

  • переключать API-адреса;
  • задавать режимы сборки;
  • управлять флагами функциональности;
  • изменять параметры dev server;
  • включать или отключать плагины;
  • хранить публичные ключи и параметры интеграций;
  • разделять настройки development, staging и production.

Особенность Vite заключается в том, что переменные окружения доступны не только внутри клиентского кода, но и непосредственно внутри конфигурационного файла vite.config.js или vite.config.ts.


Источники переменных окружения

Vite загружает переменные окружения из нескольких источников:

  1. системные environment variables;
  2. .env файлов;
  3. параметров командной строки;
  4. CI/CD окружения.

Поддерживаются следующие файлы:

.env
.env.local
.env.development
.env.production
.env.staging
.env.development.local
.env.production.local

Приоритет загрузки:

process.env
↓
.env
↓
.env.local
↓
.env.[mode]
↓
.env.[mode].local

Чем конкретнее файл, тем выше его приоритет.


Загрузка переменных внутри конфигурации

Для загрузки переменных окружения внутри Vite-конфига используется функция loadEnv.

Пример:

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    server: {
      port: Number(env.VITE_PORT)
    }
  }
})

Сигнатура функции loadEnv

Функция имеет следующий вид:

loadEnv(mode, envDir, prefixes)

Аргументы

Аргумент Назначение
mode текущий режим
envDir директория с .env файлами
prefixes фильтрация переменных по префиксам

Использование mode внутри конфига

Vite передаёт объект контекста в defineConfig.

Пример:

export default defineConfig(({ mode }) => {
  console.log(mode)

  return {}
})

Возможные значения:

development
production
staging
test

Связь mode и .env файлов

Если приложение запускается так:

vite --mode staging

Vite загрузит:

.env
.env.local
.env.staging
.env.staging.local

Это позволяет формировать полностью отдельные конфигурации окружений.


Пример .env файлов

.env

VITE_API_URL=http://localhost:3000
VITE_PORT=5173

.env.production

VITE_API_URL=https://api.example.com
VITE_PORT=8080

Использование переменных внутри server

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    server: {
      host: env.VITE_HOST,
      port: Number(env.VITE_PORT),
      open: env.VITE_OPEN === 'true'
    }
  }
})

Все значения являются строками

Даже если значение выглядит как число или boolean, Vite загружает его как строку.

Пример:

VITE_PORT=3000
VITE_HTTPS=true

Фактически:

typeof env.VITE_PORT // string
typeof env.VITE_HTTPS // string

Поэтому требуется ручное преобразование типов.


Преобразование типов

Number

const port = Number(env.VITE_PORT)

Boolean

const isHttps = env.VITE_HTTPS === 'true'

JSON

VITE_FEATURES=["auth","chat","admin"]
const features = JSON.parse(env.VITE_FEATURES)

Почему process.env используется ограниченно

В Vite предпочтительнее использовать loadEnv, а не прямой доступ к process.env.

Проблема:

process.env.VITE_API_URL

может работать нестабильно в зависимости от окружения и платформы.

Правильный подход:

const env = loadEnv(mode, process.cwd())

Использование envDir

По умолчанию Vite ищет .env файлы в корне проекта.

Однако директорию можно изменить.

Структура:

project/
  config/
    env/
      .env
      .env.production

Конфиг:

import { defineConfig, loadEnv } from 'vite'
import path from 'path'

export default defineConfig(({ mode }) => {
  const envDir = path.resolve(__dirname, 'config/env')

  const env = loadEnv(mode, envDir)

  return {
    envDir
  }
})

Фильтрация по префиксу

По умолчанию Vite экспортирует только переменные с префиксом VITE_.

Пример:

VITE_API_URL=https://example.com
SECRET_KEY=123

В клиентском коде будет доступно только:

import.meta.env.VITE_API_URL

Третий аргумент loadEnv

Можно загрузить все переменные:

const env = loadEnv(mode, process.cwd(), '')

Теперь будут доступны:

env.SECRET_KEY

Риски загрузки всех переменных

Использование пустого префикса:

loadEnv(mode, process.cwd(), '')

может случайно раскрыть чувствительные данные.

Например:

DB_PASSWORD=secret
JWT_SECRET=123456

Если такие значения попадут в клиентский bundle, это приведёт к утечке.


Разделение серверных и клиентских переменных

Распространённая практика:

VITE_PUBLIC_API=https://example.com
SERVER_DB_PASSWORD=secret
SERVER_JWT_KEY=123

Клиент получает только:

VITE_PUBLIC_API

Секреты остаются внутри Node.js окружения.


Использование переменных внутри define

Переменные окружения часто применяются при compile-time замене.

Пример:

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    define: {
      __APP_VERSION__: JSON.stringify(env.VITE_APP_VERSION),
      __ENABLE_DEBUG__: env.VITE_DEBUG === 'true'
    }
  }
})

Использование переменных для proxy

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    server: {
      proxy: {
        '/api': {
          target: env.VITE_API_PROXY,
          changeOrigin: true
        }
      }
    }
  }
})

Условная конфигурация по mode

export default defineConfig(({ mode }) => {
  const isProd = mode === 'production'

  return {
    build: {
      sourcemap: !isProd,
      minify: isProd
    }
  }
})

Условное подключение плагинов

import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig(({ mode }) => {
  const isProd = mode === 'production'

  return {
    plugins: [
      vue(),
      isProd &&
        legacy({
          targets: ['defaults']
        })
    ]
  }
})

Фильтрация falsy значений

Иногда используют:

plugins: [
  vue(),
  isProd && legacy()
].filter(Boolean)

Это удаляет:

false
null
undefined

из массива плагинов.


Доступ к переменным в клиентском коде

В браузере переменные доступны через:

import.meta.env

Пример:

console.log(import.meta.env.VITE_API_URL)

Отличие loadEnv и import.meta.env

loadEnv import.meta.env
используется в Node.js используется в браузере
доступен в vite.config доступен в клиентском коде
требует вызова встроен автоматически

Встроенные переменные Vite

Vite автоматически предоставляет ряд служебных переменных.

MODE

import.meta.env.MODE

DEV

import.meta.env.DEV

PROD

import.meta.env.PROD

BASE_URL

import.meta.env.BASE_URL

Использование переменных в build

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    build: {
      outDir: env.VITE_OUTPUT_DIR,
      sourcemap: env.VITE_SOURCEMAP === 'true'
    }
  }
})

Динамическое имя output директории

VITE_OUTPUT_DIR=dist-admin
build: {
  outDir: env.VITE_OUTPUT_DIR
}

Это особенно полезно для multi-app проектов.


Использование переменных для base

VITE_BASE=/admin/
export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    base: env.VITE_BASE
  }
})

Работа с monorepo

В monorepo .env файлы могут храниться отдельно.

Пример:

root/
  apps/
    admin/
    client/
  env/
    .env

Конфиг:

loadEnv(mode, '../. ./env')

Использование dotenv напрямую

Иногда вместо loadEnv используют dotenv.

Пример:

import dotenv from 'dotenv'

dotenv.config()

console.log(process.env.API_URL)

Однако это нарушает стандартный механизм Vite:

  • теряется поддержка mode;
  • отсутствует автоматический merge;
  • нарушается приоритет файлов;
  • появляются расхождения между dev и build.

Типизация переменных в TypeScript

Файл:

src/vite-env.d.ts

Пример:

interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_PORT: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

Типизация внутри vite.config.ts

const port = Number(env.VITE_PORT)

Либо:

const port: number = parseInt(env.VITE_PORT, 10)

Проверка обязательных переменных

if (!env.VITE_API_URL) {
  throw new Error('VITE_API_URL is required')
}

Централизованная валидация

function validateEnv(env) {
  const required = [
    'VITE_API_URL',
    'VITE_PORT'
  ]

  for (const key of required) {
    if (!env[key]) {
      throw new Error(`Missing env: ${key}`)
    }
  }
}

Использование Zod для валидации

npm install zod
import { z } from 'zod'

const EnvSchema = z.object({
  VITE_API_URL: z.string().url(),
  VITE_PORT: z.string()
})

const parsed = EnvSchema.parse(env)

Проблемы кеширования

После изменения .env файлов dev server часто требует перезапуска.

Причина:

  • Vite читает env при старте;
  • переменные кешируются;
  • runtime-перезагрузка env не выполняется.

Ошибка undefined

Частая проблема:

import.meta.env.API_URL

Возвращает:

undefined

Причина:

нет префикса VITE_

Правильно:

VITE_API_URL=https://example.com

Использование envPrefix

Префикс можно изменить.

export default defineConfig({
  envPrefix: 'APP_'
})

Теперь доступны:

APP_API_URL=https://example.com

Несколько префиксов

envPrefix: ['VITE_', 'APP_']

Полезная схема организации env

development

VITE_API_URL=http://localhost:3000
VITE_DEBUG=true

staging

VITE_API_URL=https://staging-api.example.com
VITE_DEBUG=true

production

VITE_API_URL=https://api.example.com
VITE_DEBUG=false

Практика хранения env файлов

Обычно:

.env.local

добавляют в:

.gitignore

Особенно если файл содержит:

  • локальные ключи;
  • токены;
  • внутренние адреса;
  • developer-specific настройки.

Использование переменных в CI/CD

Пример GitHub Actions:

env:
  VITE_API_URL: https://api.example.com

Vite автоматически прочитает эти значения через process.env.


Debug env значений

Иногда полезно вывести env:

console.log(loadEnv(mode, process.cwd(), ''))

Это помогает:

  • обнаружить конфликт файлов;
  • проверить mode;
  • увидеть отсутствующие значения;
  • отследить переопределение переменных.

Особенности Windows

На Windows переменные окружения в shell отличаются.

Пример:

set NODE_ENV=production

PowerShell:

$env:NODE_ENV="production"

Linux/macOS:

NODE_ENV=production

Для кроссплатформенности часто используют:

cross-env NODE_ENV=production vite build

Влияние переменных на tree-shaking

Условные флаги позволяют удалять код на этапе сборки.

Пример:

if (import.meta.env.PROD) {
  enableAnalytics()
}

Во время production build ненужные ветки могут быть исключены Rollup и esbuild.


Использование env для feature flags

VITE_ENABLE_CHAT=true
VITE_ENABLE_ADMIN=false
if (import.meta.env.VITE_ENABLE_CHAT === 'true') {
  initChat()
}

Безопасность переменных окружения

Нельзя хранить в клиентских env:

  • пароли;
  • private API keys;
  • JWT secrets;
  • database credentials;
  • SSH keys.

Все значения с клиентским префиксом фактически становятся публичными после сборки.


Архитектурный подход

Часто создают отдельный модуль:

src/config/env.ts

Пример:

export const ENV = {
  apiUrl: import.meta.env.VITE_API_URL,
  debug: import.meta.env.VITE_DEBUG === 'true'
}

Это создаёт:

  • единый источник env;
  • централизованную типизацию;
  • удобную валидацию;
  • упрощённый рефакторинг.