import.meta.env.VITE_DELIVERY_LABEL возвращает undefined, хотя строка есть в .env. Проверим всю цепочку на карточке доставки: где лежит файл, какое имя читает браузер, какой режим запущен и когда Vite подхватывает изменения.

Получите значение на странице

Нужны Node.js 24, npm и редактор. Пример рассчитан на Vite 8.3.7 и обычный JavaScript, без фреймворка и внешнего сервера. Знания HTML и импорта модулей достаточно. Создайте отдельную папку vite-env-lab: все команды ниже выполняются в ней.

Ожидаемый результат — страница с названием службы доставки, признаком доступности и именем режима. Так мы увидим значение там, где оно понадобится приложению. Покажем одно публичное значение. Если вывести окружение целиком, лишняя конфигурация может попасть в консоль.

Создайте package.json:

{
  "name": "vite-env-lab",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "vite": "8.3.7"
  }
}

Номер версии фиксирует учебный стенд. В существующем проекте используйте его зависимости и файл блокировки, прежде чем решать вопрос обновления. Ошибка undefined сама по себе ещё не повод переустанавливать Vite.

Создайте index.html в той же папке:

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Проверка доставки</title>
  </head>
  <body>
    <h1>Доставка заказа</h1>
    <p id="delivery"></p>
    <p id="mode"></p>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

Теперь создайте папку src и файл src/main.js:

const label = import.meta.env.VITE_DELIVERY_LABEL;
const enabled = import.meta.env.VITE_DELIVERY_ENABLED === 'true';

const delivery = document.querySelector('#delivery');
const mode = document.querySelector('#mode');

if (label === undefined || label.trim() === '') {
  delivery.textContent = 'Не задано VITE_DELIVERY_LABEL';
} else {
  delivery.textContent = enabled
    ? `${label}: доставка доступна`
    : `${label}: доставка временно выключена`;
}

mode.textContent = `Режим: ${import.meta.env.MODE}`;

В корне проекта создайте .env:

VITE_DELIVERY_LABEL=Учебная доставка
VITE_DELIVERY_ENABLED=true

Точка в начале имени файла значима. .env.txt — другой файл. На macOS и Windows редактор или файловый менеджер может скрывать расширение; проверьте настоящее имя через редактор проекта.

Установите зависимости и запустите сервер:

npm install
npm run dev -- --host 127.0.0.1 --port 5173 --strictPort

Откройте http://127.0.0.1:5173. На странице должны появиться «Учебная доставка: доставка доступна» и «Режим: development». При занятом порте команда завершится с ошибкой благодаря --strictPort, поэтому вы не перепутаете этот сервер с другим проектом.

Если Vite вообще не запускается, сначала исправьте ошибку терминала. Отсутствующее значение окружения проверяют уже на работающей странице. Этот порядок отделяет две разные причины сбоя.

Воспроизведите undefined одним изменением

Переименуйте первую строку .env, убрав префикс:

DELIVERY_LABEL=Учебная доставка
VITE_DELIVERY_ENABLED=true

Остановите сервер сочетанием Ctrl+C и снова выполните команду запуска. Страница покажет «Не задано VITE_DELIVERY_LABEL».

Подумайте до исправления: какое имя теперь запрашивает код и какое имя записано в файле? Сравните их посимвольно. Код по-прежнему читает VITE_DELIVERY_LABEL, а файл содержит DELIVERY_LABEL. Кроме совпадения имени нужен разрешённый префикс: по умолчанию Vite открывает клиентскому коду переменные, начинающиеся с VITE_.

Верните VITE_DELIVERY_LABEL и снова перезапустите сервер. Сообщение исчезнет.

Если вместо исправления написать в браузерном модуле process.env.DELIVERY_LABEL, возникнет другая проблема: process.env относится к окружению Node.js, а этот файл выполняется в браузере. В клиентском коде Vite используйте import.meta.env. При этом встроенные переменные вроде MODE имеют свои имена без VITE_.

Не переносите привычки из одного инструмента по совпадению расширения файла. И Vite, и серверная программа могут читать .env, но способ доступа к значениям определяется средой выполнения.

Проверьте папку, из которой Vite читает .env

В нашем стенде дерево выглядит так:

vite-env-lab/
  .env
  index.html
  package.json
  src/
    main.js

Переместите .env в src и перезапустите сервер. Для показанной конфигурации значение пропадёт: Vite ищет файлы окружения в корне окружения проекта, который по умолчанию совпадает с корнем Vite. Папка исходного модуля тут ничего не определяет. Верните файл на место.

У реального приложения корень может отличаться от папки репозитория. Например, команда запуска принимает путь к подпапке, а конфигурация задаёт root. Параметр envDir дополнительно меняет место чтения файлов окружения и может указывать на другую папку за пределами исходников. Поэтому находить ближайший к main.js файл .env ненадёжно.

Откройте команду в package.json, затем конфигурацию Vite и проверьте root и envDir. Сопоставьте пути с папкой, из которой запускается команда. В монорепозитории .env рядом с общим package.json может относиться к другому приложению.

Не добавляйте envDir только ради исчезновения симптома. Сначала выясните, почему действующий путь отличается от ожидаемого: иначе приложение начнёт читать конфигурацию соседа. Для нашего стенда отдельный vite.config.js вообще не нужен.

Сравните development, production и staging

Создайте второй файл в корне, .env.staging:

VITE_DELIVERY_LABEL=Проверочная доставка
VITE_DELIVERY_ENABLED=false

Остановите текущий сервер и запустите его с явным режимом:

npm run dev -- --mode staging --host 127.0.0.1 --port 5173 --strictPort

Ожидаемый текст — «Проверочная доставка: доставка временно выключена», режим — staging. Значение из файла режима перекрывает одноимённое значение из общего .env.

Чтобы увидеть сборку с тем же режимом, остановите сервер и выполните:

npm run build -- --mode staging
npm run preview -- --host 127.0.0.1 --port 4173 --strictPort

Откройте http://127.0.0.1:4173. Там должна быть проверочная доставка. preview показывает результат уже сделанной сборки. Он показывает готовые файлы.

Остановите preview в терминале через Ctrl+C. Теперь выполните стандартную сборку:

npm run build

У команды vite build режим по умолчанию production. Если .env.production отсутствует, в нашем наборе файлов используется общее значение из .env. Файл .env.staging не становится запасным источником только потому, что лежит рядом.

Режим и NODE_ENV решают разные задачи. Для выбора .env.staging задавайте --mode staging, а не рассчитывайте, что одна переменная NODE_ENV=staging выберет нужный файл. Собранное с --mode staging приложение всё ещё может иметь import.meta.env.PROD === true: режим конфигурации и сборка для рабочей среды могут сочетаться.

Названия здесь похожи. Проверяйте отдельно MODE, конкретную переменную приложения и результат команды сборки. Одно слово в имени файла не описывает всё поведение инструмента.

Найдите значение, которое перекрывает файл

Добавьте .env.local рядом с .env:

VITE_DELIVERY_LABEL=Локальная доставка

Запустите стандартную команду разработки заново. Теперь на странице появится «Локальная доставка». Общий файл сохранился, но более приоритетный источник изменил итог.

Для этой проверки достаточно такой карты загрузки:

ИсточникКогда применяетсяЧто проверить
.envВо всех режимахБазовое публичное значение
.env.localВо всех режимахЛокальное переопределение
.env.[mode]В выбранном режимеСовпадает ли суффикс с --mode
.env.[mode].localВ выбранном режимеЛокальная настройка этого режима
Окружение процессаУже задано перед запуском ViteПеременная терминала или системы сборки

Файлы конкретного режима имеют приоритет над общими. Уже заданная переменная окружения процесса имеет приоритет над файлами. Поэтому поиск по одному .env иногда показывает правильный текст, хотя приложение получает другое значение.

На macOS, Linux или в Git Bash проверьте перекрытие одной командой:

VITE_DELIVERY_LABEL=Из_терминала npm run dev -- --host 127.0.0.1 --port 5173 --strictPort

Предварительно остановите прежний сервер. На странице появится Из_терминала. Эта форма задания переменной относится к указанным оболочкам; в PowerShell синтаксис отличается. Чтобы повторить пример в любой системе без смены оболочки, используйте .env.local.

Для диагностики в системе непрерывной интеграции (CI) откройте настройки переменных именно той задачи, которая запускает сборку. Не печатайте всё окружение в журнал. Достаточно проверить наличие выбранного публичного имени и режим. Секреты должны оставаться в защищённом серверном процессе.

После эксперимента удалите созданную строку из .env.local или весь учебный файл, если других значений в нём нет. И снова перезапустите сервер, иначе вы сравниваете новый файл с уже запущенным процессом.

Преобразуйте строки в нужные типы

Запись VITE_DELIVERY_ENABLED=false попадает в клиент как строка 'false'. Она непустая, а значит, в условии ведёт себя как истинное значение. Поэтому такая проверка ошибочна:

const enabled = Boolean(import.meta.env.VITE_DELIVERY_ENABLED);

В полном примере используется точное сравнение со строкой 'true'. Договорённость видна по коду: только это значение включает доставку. Опечатки и отсутствие переменной её выключают.

Для числового ограничения потребуется отдельное преобразование и проверка:

const rawLimit = import.meta.env.VITE_DELIVERY_LIMIT;
const limit = Number(rawLimit);

const validLimit = rawLimit !== undefined
  && rawLimit.trim() !== ''
  && Number.isInteger(limit)
  && limit > 0;

if (!validLimit) {
  throw new Error('VITE_DELIVERY_LIMIT must be a positive integer');
}

Пустую строку проверяем отдельно: Number('') даёт ноль. Если ноль допустим в вашей задаче, одно сравнение с нижней границей уже не отличит пустую настройку от осмысленного нуля.

Декларация типа в TypeScript помогает редактору, но сама не добавляет значение в окружение и не проверяет его при запуске. Строка с неправильным именем останется неправильной, даже если у свойства появилась подсказка типа.

Оставьте в клиенте только публичные настройки

Любое значение, доступное клиентскому коду через VITE_, считайте открытым для посетителя. При сборке Vite подставляет такие значения в код. Их можно увидеть в выдаваемых файлах, даже если интерфейс ничего не печатает.

Название доставки, публичный адрес API и признак интерфейсной возможности могут жить в клиентской конфигурации. Пароль базы данных, приватный токен сервиса и ключ с полномочиями администратора — на сервере. Если добавить секрету префикс VITE_, его значение станет доступно браузеру.

Если Git игнорирует .env.local, файл защищён от случайного коммита. Содержимое браузерной сборки при этом остаётся открытым. Это две разные границы. В учебном проекте добавьте в .gitignore:

node_modules/
dist/
*.local

Если секрет уже попал в сборку, удалением строки из .env вопрос не заканчивается. Нужно отозвать или заменить сам секрет и пересобрать приложение. У опубликованной строки могла остаться копия.

Публичный адрес API тоже не устраняет ограничения браузера. Если значение читается правильно, а запрос блокируется, проверьте сетевую часть отдельно: для этого пригодится материал «Как связать локальный API и фронтенд через origin и proxy».

Сохраните маршрут диагностики

Что вы видитеПроверкаДействие
undefined только у одной переменнойТочное имя и префикс VITE_Исправьте обе стороны чтения
Весь набор проекта отсутствуетКорень Vite, envDir, имя файлаПоложите файл в фактическую папку окружения
Значение осталось прежнимКакой процесс и порт открытыПерезапустите нужный сервер
Локально работает, в сборке другоеРежим, файлы режима, переменные CIСверьте вход именно команды сборки
Строка 'false' включает возможностьЕсть ли преобразование в кодеСравните со строкой 'true'
После правки на сервере ничего не меняетсяКогда создана статическая сборкаПересоберите и заново разместите файлы

Идите сверху вниз, меняя за раз одно условие. Если одновременно переименовать переменную, изменить режим и перенести файл, исчезнувший симптом ничего не расскажет о причине.

Для финальной проверки выполните два действия. Сначала добавьте .env.production с другим названием доставки, соберите проект без --mode и убедитесь через preview, что показано именно оно. Затем задайте в .env.staging значение false и проверьте, что доставка выключена, хотя строка непустая. Перед каждым запуском предскажите текст страницы, после — сравните результат.

Если хотите системно разобраться с конфигурацией сборки, откройте программу курса по Vite. А в своём проекте начните с одной отсутствующей переменной: зафиксируйте её имя, режим и источник, который победил при загрузке.


«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

ТелеграмПодкастБесплатные учебники