CDN‑кеширование для Next.js: заголовки кеша без утечек данных пользователей
Кеширование CDN для Next.js ускоряет страницы и активы, но неправильные заголовки могут закешировать пользовательские данные. Узнайте, что можно кешировать, примеры заголовков и распространённые ловушки.

Почему кеширование CDN может сломать приложение на Next.js
CDN делают сайт быстрым, сохраняя копии того, что сервер отправляет, и затем отдают эти копии с узла, близкого к посетителю. Если всё сделать правильно, это сокращает время загрузки и снижает нагрузку на сервер. Если не подумать — можно сломать входы в систему, показать чужие данные или получить «случайное» восстановление спустя время.
CDN может кешировать два типа вещей:
- Файлы (изображения, JS, CSS)
- Полные HTTP‑ответы (HTML страницы, JSON от API‑роута)
Сложность в том, что приложение Next.js часто отдаёт и публичные страницы, и страницы для конкретного пользователя с одного и того же домена. Если CDN будет одинаково обрабатывать их, вы можете случайно закешировать то, что никому не должно показываться.
Основные проблемы, вызывающие большинство ошибок:
- Устаревший контент: вы обновили страницу, но пользователи всё ещё видят старую версию из кеша.
- Кешированные персональные ответы: CDN сохраняет ответ для одного пользователя, а потом отдаёт его другому.
Частый кейс: у вас есть маркетинговая главная страница и дашборд по пути /app. Пользователь заходит, сервер возвращает HTML с его именем и последними действиями. Если этот HTML попадёт в кеш, следующий посетитель /app может увидеть чужой дашборд или застрять в неверном состоянии аутентификации.
Поэтому кеширование CDN для Next.js — это не просто «включи кеширование», а продуманное назначение правил: что безопасно кешировать (обычно статические ресурсы и действительно публичные страницы), что никогда не кешировать (персонализированный HTML, авторизация и пользовательские API), и как долго хранить кеш.
Разбейте приложение на кешируемые и некешируемые части
Прежде чем менять заголовки, разберитесь, что именно отдаёт ваше приложение. CDN работает лучше, когда каждый тип ответа обрабатывается по‑разному. Одна общая настройка для всего — частая причина случайного кеширования пользовательских данных.
Начните с перечисления основных типов ответов, которые вы отдаёте:
- Статические файлы (JS, CSS, шрифты)
- Изображения (включая оптимизированные варианты)
- HTML‑страницы (маркетинг, документация, дашборды)
- API‑ответы (JSON, вебхуки, колбэки авторизации)
Далее сгруппируйте маршруты по степени публичности:
| Группа маршрутов | Примеры | Можно кешировать на CDN? | Часто меняется? |
|---|---|---|---|
| Публичные | /, /pricing, /blog/* | Обычно да | Иногда |
| Полу‑публичные | /account, /checkout | Обычно нет | Часто |
| Приватные | /dashboard, /admin | Нет | Часто |
Отметьте зоны с «высокой частотой изменений». Цены, наличие на складе и всё, что показывает живой статус (заказы, использование, сообщения) может быстро устаревать. Дашборды — очевидный пример, но к ним добавляются страницы оформления заказа, шаги онбординга и даже блоки «С возвращением», которые персонализированы.
Разные типы контента требуют разных правил, потому что риски разные. Агрессивное кеширование JS‑бандла обычно безопасно. Агрессивное кеширование HTML, зависящего от куки, может привести к утечке одного пользователя другому.
Пример из реальной жизни: на сайте маркетинговая главная страница, но в шапке показывают имя пользователя при входе. Эта деталь переводит в целом кешируемую страницу в рискованную, если вы не разделите опыт (публичная оболочка + приватные данные подтягиваются отдельно) или не отключите кеширование при наличии куки.
Безопасное кеширование статических активов (JS, CSS, шрифты, изображения)
Статические активы — самый лёгкий выигрыш, потому что обычно не меняются в зависимости от пользователя. Это файлы, которые браузер скачивает и использует на многих страницах.
Типичные статические ресурсы:
- Файлы сборки Next.js в
/_next/static/(JS‑чанки, CSS) - Шрифты и иконки (woff2, ttf, svg)
- Изображения и фавиконы
- Всё, что в папке
public/
Для версияных файлов (с хешем в имени) целесообразно долгое кеширование. Хеш меняется при изменении содержимого, то есть файл фактически неизменяем.
Обычный заголовок для таких файлов:
Cache-Control: public, max-age=31536000, immutable
Перед тем как ставить годичный TTL, убедитесь, что у вас действительно неизменяемые файлы. Если вы отдаёте logo.png из public/ и потом перезаписали файл под тем же именем, долгий TTL сохранит старый логотип в кеше надолго. Для файлов без версии держите TTL короче или добавьте шаг сборки, который делает fingerprint имён.
Изображения часто требуют отдельной стратегии. Если вы генерируете несколько размеров или форматов (WebP, AVIF), кеш безопасен только когда итоговый URL однозначно идентифицирует результат. Если endpoint оптимизации изображения меняется параметрами ширины, качества или формата, эти входные данные должны быть частью ключа кеша. Иначе пользователи могут получить неправильный вариант.
Быстрая проверка: откройте один JS‑чанк и один файл шрифта и убедитесь, что они не меняются вне деплоя.
Кеширование страниц и HTML: что безопасно, а что рискованно
Самый большой выигрыш (и самый большой риск) — это кеширование полного HTML. HTML — то, что видит пользователь, поэтому при неправильном кешировании можно показать чужой контент.
Безопасный для кеширования HTML обычно одинаков для всех: маркетинговые страницы, цены, документация, публичные лендинги, большинство блогов. Простой тест: откройте страницу в режиме инкогнито — если она выглядит одинаково при входе и без входа, её можно кешировать.
Рискованное HTML — всё, что меняется в зависимости от пользователя, сессии или приватных данных: дашборды, настройки аккаунта, оформление заказа, история заказов, страницы с именем, email, статусом биллинга или «последний вход». Кеширование таких страниц на CDN — путь к утечкам.
Правило большого пальца:
- Кешируйте публичные страницы с контролируемым временем жизни.
- Не кешируйте пользовательские страницы, если вы не уверены, что ответ одинаков для всех.
- В сомнительных случаях трактуйте HTML как приватный и отключайте кеш.
ISR (Incremental Static Regeneration) находится посередине. Он позволяет Next.js быстро отдавать кеш‑стрaницу и обновлять её в фоне по таймеру. Это отлично для страниц вроде постов блога, которые меняются редко. Плохо подходит для персонализированных страниц, потому что проблема «не тот пользователь» не решается свежестью.
Ещё один подводный камень: кеширование HTML — это не то же самое, что кеширование вызовов данных. Страница может быть безопасной для кеширования, а её вызываемые API — нет. Или вы можете держать HTML приватным, но кешировать публичные данные (например, список продуктов) с коротким TTL. Путаница в этих вещах — причина того, почему «всё работало в стейджинге» превращается в устаревший контент или случайную утечку.
Заголовки кеширования, которые нужно знать (без жаргона)
Большинство проблем с кешем возникает из‑за того, что CDN и браузер догадываются, как долго что‑то безопасно переиспользовать. Заголовки кеша говорят им об этом явно.
Cache-Control: главный заголовок
Cache-Control — набор инструкций. Наиболее распространённые части:
max-age=...: как долго браузер может хранить ответ (в секундах)s-maxage=...: как долго общий кеш (CDN) может хранить ответ. Если указан, CDN обычно ориентируется на него, а не наmax-age.public: можно хранить в общем кешеprivate: хранить только в браузере пользователя (CDN не должен)no-store: не хранить нигде (используйте для персональных или чувствительных ответов)
Простое правило: если ответ хоть раз может отличаться для разных пользователей, избегайте public. Для дашбордов и страниц аккаунта по умолчанию ставьте no-store, если нет очень веской причины иначе.
stale-while-revalidate: отдавать старый, обновлять в фоне
stale-while-revalidate=... позволяет кешу отдать слегка устаревшую версию на короткое время, пока он получает свежую в фоне.
Это работает для контента, который может немного отставать (например, маркетинговая главная). Рисковать этим не стоит там, где нужна моментальная точность (биллинг, права доступа).
ETag и Last-Modified: как кеш проверяет, изменилось ли что‑то
С ETag или Last-Modified кеш может спросить у сервера: «Изменилась ли версия?» Если нет, сервер отвечает небольшим 304 Not Modified вместо полного тела.
Это важно, когда ответы большие, контент меняется часто или вы хотите быстрые обновления, не отключая кеширование.
Vary: метка «зависит от»
Vary указывает кешам, от каких заголовков зависит ответ. Это важно, когда вовлечены куки или авторизация.
Если HTML меняется в зависимости от состояния входа, и вы неправильно настроите Vary вместе с правилами кеша, CDN может отдать версию одного пользователя другому. Часто используемые значения Vary — Cookie, Authorization, Accept-Encoding.
Пошагово: настройте правила кеша для типичного приложения Next.js
Практический подход — начать с самых безопасных вещей, затем перейти к контенту, который меняется.
1) Начните с самых безопасных целей
Начните с файлов, одинаковых для всех: JS‑бандлы, CSS, шрифты и версияные изображения. Это низкий риск и обычно даёт наибольший прирост скорости.
2) Устанавливайте время кеширования по возможности изменения контента
Долго кешируйте неизменяемые активы (файлы, у которых имя меняется при изменении содержимого). Для страниц и HTML ставьте короткие сроки, поскольку содержимое может обновиться без смены имени файла.
Чек‑лист, который выдерживает реальный мир:
- Начните с
/_next/static/*, ваших явно версионированных активов и других файлов с хешированными именами. - Для неизменяемых активов отправляйте долгий
Cache-Controlплюсimmutable. - Для публичных, неперсонализированных страниц (маркетинг, документация) используйте короткий общий кеш (минуты–часы) и рассматривайте
stale-while-revalidate. - Для всего, что зависит от пользователя (дашборды, страницы аккаунта, API‑роуты, зависящие от куки), принудительно ставьте
Cache-Control: privateилиno-store. - Решите, как вы будете инвалидировать кеш: опирайтесь на деплои, которые меняют имена файлов, и имейте понятный план очистки HTML при срочных правках.
3) Тестируйте прежде чем доверять
Не останавливайтесь на «загружается быстро». Сделайте жёсткое обновление и сравните поведение в режиме вышедшего и вошедшего пользователя.
Быстрая проверка: откройте одну и ту же страницу в инкогнито и в сессии с авторизацией. Если HTML, заголовки или видимые данные когда‑либо совпадают там, где не должны — считайте это утечкой и прекратите кеширование этого маршрута.
Ключи кеша CDN и правила, которые решают, что отдавать
CDN не "кеширует страницу" абстрактно. Он сохраняет ответ под ключом кеша. Ключ обычно строится из пути запроса и иногда из строки запроса, заголовков и куки.
Если ключ слишком общий — пользователи увидят чужой контент. Если слишком специфичен — низкий процент попаданий в кеш и медленнее страницы.
Что обычно должно входить в ключ кеша
Начните просто: для публичных страниц обычно достаточно пути. Добавляйте другие части только если они действительно меняют то, что должен видеть пользователь.
- Путь:
/pricingи/blog/slugдолжны быть разными ключами. - Параметры запроса: включайте лишь те, которые меняют содержимое (например,
?page=2). Игнорируйте трекинговые параметры вродеutm_*. - Заголовки: включайте только реальные варианты (например, язык).
- Куки: избегайте использования куки для публичного контента — они взрывают количество вариантов кеша.
Опасный случай — когда CDN кеширует персонализованный ответ, потому что в запросе была кука. Даже если кука не является частью ключа, кеширование персонализованного ответа может привести к утечке.
Варианты: локаль и устройство
Если HTML зависит от локали, используйте явный сигнал и укажите Vary на это (часто Accept-Language или заголовок, который вы сами контролируете).
Будьте осторожны с вариацией по устройству. Vary: User-Agent создаёт слишком много версий и легко делается неправильно. Предпочитайте адаптивный дизайн или небольшой явный заголовок, если действительно нужен отдельный HTML.
Наконец, настройте правила обхода кеша для всего, что нельзя кешировать: /admin, /api‑роуты, возвращающие персональные данные, аутентифицированные дашборды и любые маршруты, которые проверяют сессионную куку.
Как не допустить кеширования персонализированных ответов
CDN хорош, пока случайно не сохранит ответ, предназначенный для одного человека, а затем не отдаст его другому. Это самый большой риск: кешированный HTML или JSON может стать утечкой данных, если в нём есть имя, email, статус подписки или что‑то, связанное с сессией.
Персонализация обычно пробирается через куки и заголовки авторизации. Если в запросе есть Cookie или Authorization, считайте, что ответ может быть специфичен для пользователя, если вы не абсолютно уверены в обратном.
Для всего, что за авторизацией, используйте явные заголовки кеша. Для дашбордов, настроек, биллинга и пользовательских API отдавайте Cache-Control: no-store (или хотя бы private, no-store). Это говорит браузеру и CDN не хранить копию.
Практические сигналы риска, которые должны заставить вас не кешировать:
- В запросе есть
Cookie,Authorizationили заголовок с токеном сессии. - Ответ зависит от того, кто пользователь (даже если URL тот же).
- HTML содержит данные аккаунта, недавнюю активность или статус биллинга.
- API возвращает записи пользователей, токены или что‑то чувствительное.
- Вы не можете объяснить ключ кеша CDN в одном предложении.
Смешанные страницы — проблема. Распространённый паттерн: публичная оболочка маркетинга, которая при входе показывает «Привет, Сам». Если HTML персонализирован, не кешируйте его публично. Более безопасный подход: кешируйте публичную оболочку и подтягивайте персонализированные фрагменты на клиенте (или с отдельного некешируемого endpoint).
Если возможно, разделяйте публичные и приватные endpoint'ы, чтобы правила оставались простыми: публичные — кешируемые, аутентифицированные — некешируемые.
Частые ошибки, приводящие к устаревшему контенту или утечкам данных
Большинство сбоев с кешированием происходит, когда что‑то «выглядело быстрым» в быстром тесте, но ведёт себя иначе, когда реальные пользователи заходят в систему. Опасность не только в старом контенте — это ещё и отдача чужих данных.
Ошибки, которые повторяются:
- Кеширование HTML для маршрутов с авторизацией потому, что в анонимном тесте всё выглядело безопасно.
- Пометка ответов как
public, хотя они меняются в зависимости от куки, заголовка авторизации или геолокации. - Длинный
max-ageдля неизверсионированных активов (например,/app.css), потом обновление и пользователи продолжают видеть старую версию. - Забвение, что редиректы, страницы ошибок и 404 тоже кешируются — это может зафиксировать временный сбой или неверный маршрут.
- Доверие поведению по‑умолчанию от хостинга или фреймворка, не проверив, что именно хранит CDN.
Быстрый способ найти риск: спросите себя «Может ли этот ответ отличаться для двух пользователей?» Если да — не кешируйте публично. Даже маленькая разница, вроде приветствия, делает HTML персонализированным.
Следите также за невидимым кешированием: закешированный 302 на /login, закешированный 404 для вновь запущенной страницы или закешированный 500 во время деплоя.
Быстрые проверки перед выпуском кеширования в продакшен
Перед запуском кеширования пройдитесь реальными запросами, а не только тем, что вы ожидаете от кода. Маленькие ошибки в заголовках могут привести к устаревшим страницам или к тому, что неверный пользователь увидит чужой контент.
Начните с базового:
- Публичные страницы, одинаковые для всех, должны отправлять
Cache-Control: publicи разумныйs-maxage, чтобы CDN мог их кешировать. - Всё, что за авторизацией, должно быть
Cache-Control: privateилиno-store.
Если не уверены — ставьте no-store и ослабляйте позже.
Проверки перед деплоем, ловящие большинство проблем:
- Откройте публичную страницу и убедитесь, что она действительно кешируемая (
Cache-Control: publicсs-maxage). - Откройте аутентифицированную страницу и убедитесь, что она не кешируется общим кешем (
privateилиno-store, никогдаpublic). - Проверьте статические активы: долгий кеш безопасен только при версионировании имён (хеш в имени). Если нет — держите короткий TTL.
- Обратитесь к API, возвращающему пользовательские данные, и убедитесь, что CDN их не кеширует.
- Протестируйте две учётные записи: войдите как Аккаунт A в одном браузере, Аккаунт B в другом и загрузите те же URL. Никаких персональных данных не должно пересекаться.
Также внимательно посмотрите на параметры запроса. Если ваш CDN кеширует по полному URL, ?ref=, ?utm_ и случайные фильтры создают бесконечные варианты кеша. Решите, какие параметры игнорировать, а какие включать в ключ кеша.
Реалистичный пример: маркетинговый сайт + дашборд на одном домене
Стартап разместил Next.js приложение, в котором маркетинговый сайт и дашборд для пользователей живут на одном домене. Трафик подскакивает после релизов, поэтому они добавили CDN кеширование.
Они разделили приложение на две группы.
Кешируем то, что одинаково для всех:
- Версионированные JS и CSS файлы (длинный кеш, имена меняются при деплое)
- Изображения, шрифты и иконки (длинный кеш при версионировании; короче, если перезаписываются)
- Маркетинговые страницы
/,/pricing,/blog(короткий TTL на CDN, чтобы правки быстро появлялись)
Для маркетингового HTML они поставили короткий общий TTL и небольшое stale-while-revalidate. CDN может ненадолго отдать старую страницу, пока обновляет её.
Никогда не кешировать то, что варьируется по пользователю:
/dashboardи всё под ним- Страницы настроек и биллинга
/api/user(и любые API, возвращающие данные аккаунта)- Страницы аутентификации и колбэки
Чтобы убедиться, что нет утечек, они тестировали как параноики: две учётные записи, два браузера, жёсткое обновление и проверка заголовков. Персонализированные страницы никогда не должны отдавать public в директивах кеширования.
Если что‑то пошло не так (пользователь увидел чужое имя или устаревший статус подписки), они сначала отзывали правило CDN. Затем ужесточали заголовки на рискованных маршрутах (начала с /dashboard и /api) прежде чем снова включать кеш.
Следующие шаги: выкатывайте осторожно и просите помощи, если всё запутано
Начните с малого и делайте простые победы скучными. Кешируйте неизменяемые статические ресурсы (хешированные JS и CSS, шрифты, версияные изображения) с долгим TTL. Когда это стабильно, добавьте кеширование для действительно публичных страниц. Всё, что может меняться в зависимости от пользователя, куки или авторизации — оставляйте некешируемым, если только вы специально не спроектировали это для edge‑кеширования.
Запишите правила кеширования простым языком. Многие ошибки появляются спустя месяцы, когда кто‑то добавляет новый заголовок, вводит редирект или меняет логику авторизации. Короткий «контракт кеширования» облегчает ревью: что можно кешировать, что нельзя и какие сигналы (куки, заголовки, параметры) меняют ответ.
Если вы унаследовали кодовую базу, сгенерированную ИИ, перепроверьте заголовки и потоки авторизации. Такие проекты часто имеют смешанные маршруты, непоследовательные Cache-Control и сессии, которые работают локально, но ломаются за CDN.
Если вы видите сломанные авторизации, рискованное кеширование или не можете объяснить, что именно хранит ваш CDN, FixMyMess (fixmymess.ai) делает диагностику кода и починку для AI‑сгенерированных приложений, включая разбор заголовков кеша и границ авторизации. Они также предлагают бесплатный аудит кода, чтобы выявить проблемы до внесения изменений в production.