Проблема: исправление есть, но пользователи его не видят
Вы отправляете критически важное исправление в ваше React-приложение. Развертывание проходит успешно, вы обновляете страницу — всё работает. Сообщаете команде, что фикс «в проде». И тут приходит сообщение от пользователя: «У меня по-прежнему сломано». Эта ситуация знакома многим фронтенд-разработчикам и является одной из самых недооценённых проблем при деплое.
Причина кроется в браузерном кэше. Часто разработчики ошибочно полагают, что за кэширование отвечает сервер (например, Nginx), тогда как в стандартной конфигурации для раздачи статики именно браузер решает, когда ему обновить файл, а когда использовать сохранённую копию.
Как устроено кэширование в связке React + Nginx
Когда пользователь заходит на ваш сайт, ответ может быть закэширован в двух местах:
- Браузерный кэш на устройстве пользователя.
- Кэш самого Nginx, но только если вы явно включили опцию
proxy_cache.
В типичном сценарии, когда Nginx просто отдаёт файлы из папки dist/, он не использует свой оперативный кэш. Он читает файл с диска при каждом запросе. Таким образом, вся логика кэширования ложится на плечи браузера.
Это ключевое отличие. Многие думают, что Nginx управляет сроком жизни файлов, но без настройки proxy_cache он лишь посредник. Настоящий контроль находится в заголовках ответа, которые видит браузер.
Логика работы браузерного кэша
Получив файл от сервера, браузер анализирует HTTP-заголовки, чтобы решить, как долго хранить этот ресурс. Здесь возможны два основных сценария.
Сценарий 1. Вы задали явные заголовки Cache-Control
Если вы правильно настроили веб-сервер, вы полностью контролируете ситуацию. Ключевые директивы:
max-age=86400: браузер будет считать файл свежим ровно один день (в секундах).no-cache: браузер обязан каждый раз делать условный запрос к серверу (revalidation), чтобы проверить, не изменился ли файл.immutable: подсказка браузеру, что файл никогда не изменится. Это часто используется для бандлов с хешами в именах (main.abc123.js), позволяя пользователю годами держать их в кэше без лишних запросов.
Сценарий 2. Заголовки отсутствуют (настройки по умолчанию)
Это ловушка, в которую попадают многие. Если Nginx не прислал никаких инструкций по кэшированию, браузер не игнорирует файл. Он включает так называемую эвристическую логику, описанную в стандарте RFC 7234.
Формула расчёта срока хранения выглядит так:
Эвристический TTL = (Дата получения ответа - Время последнего изменения файла) × 10%
Nginx автоматически передаёт заголовок Last-Modified, основанный на времени модификации файла на диске. Допустим, вы собрали проект три месяца назад (файл лежал на диске всё это время), а сегодня развернули hotfix. Для браузера формула сработает примерно так:
(Сегодняшняя дата - Дата сборки проекта) × 0.1.
Если файлу несколько месяцев, браузер может смело считать его валидным ещё несколько недель или даже дней, основываясь на этой формуле. Именно поэтому пользователь сообщает о проблеме спустя час после вашего деплоя — его браузер уверен, что старая версия JS-файла всё ещё актуальна.
Решение: возьмите кэш под контроль через Nginx
Чтобы гарантировать мгновенное получение обновлений пользователями, необходимо перестать полагаться на эвристику браузера и задать жёсткие правила. В большинстве случаев достаточно добавить пару строк в конфигурацию блока location / вашего Nginx.
Для современных сборок React (Create React App, Vite), где имена файлов меняются при каждой сборке благодаря content hashing, идеальной стратегией является агрессивное кэширование.
location / {
try_files $uri $uri/ /index.html;
# Кэшируем статические ассеты надолго
location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Что делают эти строки:
expires 1y;устанавливает срок годности на один год.add_header Cache-Control "public, immutable";прямо говорит браузеру: «Храни этот файл сколько угодно, тебе больше никогда не нужно спрашивать у сервера, обновился ли он»
Поскольку имя файла при хотфиксе изменится (например, с app.a1b2c3.js на app.d4e5f6.js), браузер скачает новый файл как совершенно другой ресурс, а старый останется мёртвым грузом в кэше до истечения года. Ваша команда и пользователи увидят исправления немедленно после обновления страницы.