Файлы .env и порядок их загрузки

В экосистеме Vite файлы окружения используются для конфигурации переменных, которые должны отличаться между режимами запуска, окружениями и стадиями сборки. Эти файлы основаны на стандарте dotenv, но Vite накладывает собственную систему приоритетов, правил именования и механизма загрузки.

Переменные окружения в Vite доступны через import.meta.env, а их поведение строго определяется режимом выполнения (mode) и набором файлов .env, находящихся в корне проекта.


Основные типы .env файлов

Vite поддерживает несколько стандартных файлов окружения, каждый из которых имеет определённый приоритет и область применения:

  • .env — базовый файл, применяемый во всех режимах
  • .env.local — локальные переопределения (обычно игнорируется системой контроля версий)
  • .env.[mode] — файл, зависящий от режима (например, development, production, staging)
  • .env.[mode].local — локальные переопределения для конкретного режима

Примеры:

  • .env
  • .env.local
  • .env.development
  • .env.development.local
  • .env.production
  • .env.production.local

Режимы работы Vite и связь с .env

Vite использует параметр mode для определения набора переменных окружения. По умолчанию:

  • vite devmode = development
  • vite buildmode = production

Дополнительные режимы задаются явно:

vite --mode staging

В этом случае будут загружены:

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

Порядок загрузки файлов

Порядок имеет критическое значение, так как одинаковые переменные переопределяются последующими файлами.

Последовательность загрузки в Vite:

  1. .env
  2. .env.local
  3. .env.[mode]
  4. .env.[mode].local

Каждый последующий файл имеет более высокий приоритет и перезаписывает значения предыдущего.


Приоритет переменных внутри файлов

Если переменная объявлена в нескольких местах, действует правило переопределения:

  • .env задаёт базовое значение
  • .env.local переопределяет .env
  • .env.[mode] переопределяет оба предыдущих
  • .env.[mode].local имеет максимальный приоритет

Пример:

.env

VITE_API_URL=https://api.example.com

.env.development

VITE_API_URL=http://localhost:3000

.env.development.local

VITE_API_URL=http://localhost:4000

В режиме development итоговым значением будет:

http://localhost:4000

Ограничение доступа к переменным

Vite не предоставляет все переменные окружения в клиентский код автоматически. Доступны только те, которые имеют префикс:

VITE_

Пример:

VITE_API_URL=https://api.example.com
VITE_APP_NAME=MyApp

Использование в коде:

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

Все переменные без префикса игнорируются в клиентской сборке и не попадают в bundle.


Внутренний механизм загрузки

При запуске Vite выполняет несколько этапов обработки окружения:

  1. Определение текущего mode
  2. Чтение файлов .env* через dotenv-подобный парсер
  3. Объединение переменных в порядке приоритета
  4. Подстановка значений в import.meta.env
  5. Инлайн значений на этапе сборки (в режиме build)

Важно, что переменные не подгружаются динамически в runtime в production-сборке — они инлайнятся в код.


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

Vite предоставляет API loadEnv, позволяющий вручную получить переменные окружения:

import { loadEnv } from 'vite'

export default ({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '')
  return {
    define: {
      __APP_ENV__: env.APP_ENV
    }
  }
}

Параметры loadEnv:

  • mode — текущий режим
  • envDir — директория с env-файлами
  • prefix — фильтр переменных (по умолчанию VITE_)

Если prefix передан как пустая строка, загружаются все переменные.


Отличие поведения в dev и build

В dev-режиме переменные доступны через прокси-объект import.meta.env, который сохраняет реактивность окружения сервера разработки.

В build-режиме происходит полная статическая замена:

import.meta.env.VITE_API_URL

превращается в:

"https://api.example.com"

Это означает, что изменение .env после сборки не влияет на уже собранный код.


Переменные и типизация

Vite не накладывает строгую типизацию на import.meta.env, но TypeScript поддержка может быть расширена через декларации:

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

interface ImportMeta {
  readonly env: ImportMetaEnv
}

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


Особенности обработки значений

Все значения в .env интерпретируются как строки. Даже если указано число или boolean:

VITE_DEBUG=true
VITE_PORT=3000

В коде они будут:

import.meta.env.VITE_DEBUG // "true"
import.meta.env.VITE_PORT  // "3000"

Приведение типов выполняется вручную.


Расширение переменных

Vite поддерживает подстановку значений внутри .env файлов:

VITE_HOST=localhost
VITE_URL=http://${VITE_HOST}:3000

После обработки:

http://localhost:3000

При этом расширение работает только для переменных, уже определённых в текущем контексте загрузки.


Игнорирование файлов окружения

Некоторые файлы могут исключаться из процесса через .gitignore или .env.*.local, что используется для хранения секретов:

  • API ключи
  • локальные токены
  • параметры разработки

Типичный пример:

VITE_API_KEY=secret_value

в .env.production.local, который не попадает в репозиторий.


Связь с SSR и server-side кодом

При использовании SSR в Vite окружение делится на клиентское и серверное:

  • клиент получает только VITE_ переменные
  • сервер может использовать полный process.env

Это разделение предотвращает утечку секретных данных в браузер.


Частые проблемы порядка загрузки

Типичные ошибки связаны с неправильным пониманием приоритетов:

  • ожидание, что .env.local переопределит .env.production
  • использование переменных без VITE_
  • попытка изменить env во время runtime
  • конфликт одинаковых ключей в разных режимах

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


Итоговая модель поведения env в Vite

Система окружения Vite строится вокруг трёх принципов:

  • разделение по режимам (mode)
  • строгий приоритет файлов
  • фильтрация по префиксу VITE_

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