Релиз SPA: runtime config, source maps, CDN-кэш и rollback

↑ 6.2 Сборка и инфраструктура: Vite, monorepo, CI∕CD · ← Предыдущая

Желательно~3 мин чтенияУровень: middle

Зачем это на собесе

Вопрос «как вы выкатываете фронтенд» проверяет, понимаете ли вы разницу между сборкой и конфигурацией, кэшированием и откатом.

Главная идея

Один и тот же артефакт сборки (Docker-образ или архив статики) должен работать на dev, stage и prod. Всё окруженческое — runtime config, а не build-time переменные.

Runtime config

Проблема: import.meta.env.VITE_API_URL вшивается в бандл при сборке. Для нового окружения пришлось бы пересобирать.

Решение: файл config.json рядом с index.html, который приложение читает при старте. Его генерирует entrypoint контейнера из переменных окружения.

# docker-entrypoint.d/40-runtime-config.sh
printf '{ "apiUrl": "%s", "sentryDsn": "%s", "release": "%s" }\n' \
  "$API_URL" "$SENTRY_DSN" "$RELEASE" > /usr/share/nginx/html/config.json
// main.ts
const config = await fetch('/config.json', { cache: 'no-store' }).then(r => r.json())
const app = createApp(App)
app.provide(CONFIG_KEY, config)
app.mount('#app')

Важно: config.json — не для секретов, он публичный.

Кэширование

ФайлЗаголовокПочему
index.htmlno-cacheВсегда проверять, чтобы получить новые хэши
assets/*.[hash].js/csspublic, max-age=31536000, immutableИмя содержит хэш содержимого
config.jsonno-storeМеняется без пересборки

Порядок выкладки: сначала новые assets, потом index.html. Иначе пользователь получит HTML, ссылающийся на ещё не загруженные файлы.

Проблема старых чанков

После деплоя у открытой вкладки остаются ссылки на старые чанки, которых на сервере уже нет → ошибка Failed to fetch dynamically imported module.

  • храните предыдущие 1–2 релиза assets на CDN;
  • в роутере перехватывайте ошибку и делайте location.reload();
  • Vite генерирует событие vite:preloadError.
window.addEventListener('vite:preloadError', () => location.reload())

Source maps

  • собираем с build.sourcemap: 'hidden' — карты создаются, но ссылка на них в бандле не добавляется;
  • загружаем в Sentry с привязкой к release, затем удаляем из публичной раздачи;
  • так пользователь не видит исходники, а стек-трейсы в Sentry читаемы.

Rollback

  1. Артефакты версионируются (тег образа = git sha).
  2. Откат = переключение на предыдущий тег / переопределение alias на CDN.
  3. Пересборка не нужна — поэтому важен runtime config.
  4. Если релиз затронул API-контракт, откат фронта должен быть совместим с текущим бэкендом.

Фичефлаги и канареечные релизы

  • новые фичи прячем за флагом — включаем постепенно без деплоя;
  • canary: 5% трафика на новую версию, наблюдаем ошибки в Sentry и Web Vitals, затем расширяем.

Вопросы с ответами