Редактор изменил название товара в CMS, сохранил публикацию и открыл сайт — там осталось прежнее название. Это не всегда ошибка сохранения. Публичная страница, запрос к CMS и промежуточный CDN могут хранить разные версии данных. Сначала найдите, какой слой отдаёт старое значение.
Разберём собственный учебный магазин на Next.js 15 с App Router. Карточка /catalog/red-mug получает публичные сведения из CMS, а CMS отправляет серверу уведомление после изменения. Адреса, поля и способ авторизации здесь условные; реальный обработчик нужно согласовать с документацией вашей CMS.
Проверьте, где ещё осталось старое название
Сравните три результата: значение в редакторе CMS, опубликованный ответ её API и HTML страницы магазина. Запишите точное новое название, идентификатор товара, время изменения и среду. Preview редактора может показывать черновик, тогда как сайт правильно получает последнюю опубликованную версию.
Если API возвращает старые данные, обновление Next.js не решит проблему. Проверьте публикацию, локаль, версию записи и кеш самого источника. Если API уже возвращает новое значение, а страница — прежнее, переходите к настройкам приложения и хостинга.
Откройте страницу прямым переходом без авторизации. Переход внутри уже открытого приложения может использовать состояние или клиентский кеш текущего сеанса. Для сравнения нужен свежий ответ документа. При наличии нескольких доменов проверьте, что CMS уведомляет именно production, а не preview прошлой сборки.
Опишите кеш страницы явно
В Next.js 15 нельзя объяснять любую задержку словами «fetch автоматически всё кеширует». Ответы fetch по умолчанию не кешируются, но способ формирования самого маршрута тоже влияет на получаемую страницу. Фактические настройки нужно смотреть в конкретном месте загрузки. Справка fetch для Next.js 15.
Наш учебный запрос сознательно использует серверный кеш с периодическим обновлением. Его следует разместить в серверной функции, которой пользуются страница и при необходимости её метаданные. Точный URL источника выбирается из доверенной конфигурации, а не из произвольного параметра посетителя.
const response = await fetch(productApiUrl, {
cache: 'force-cache',
next: { revalidate: 3600 },
})
if (!response.ok) throw new Error('Product source failed')
const product = await response.json()Здесь один час — учебный выбор свежести, а не рекомендованная настройка для цены или остатка любого магазина. Сведения о покупке могут требовать другой политики. Согласуйте, какие данные допустимо показывать из кеша и как долго. Не используйте одновременно взаимоисключающие параметры вроде no-store и ненулевого интервала обновления одного запроса.
Сделайте webhook с проверкой отправителя
Webhook — HTTP-запрос, которым CMS сообщает о событии. Получение такого запроса ещё не доказывает, что страницу обновили. Сначала сервер проверяет отправителя и допустимую запись, затем помечает связанные маршруты для обновления.
Ниже собственный упрощённый обработчик app/api/cms-updated/route.ts. Он предполагает, что учебная CMS умеет отправлять согласованный секрет в заголовке Authorization. Используйте именно способ подтверждения, который поддерживает ваша настоящая CMS; если требуется подпись HMAC, её нужно проверять по исходному телу запроса до разбора JSON.
import { revalidatePath } from 'next/cache'
export async function POST(request: Request) {
const secret = process.env.CMS_WEBHOOK_SECRET
if (!secret) return Response.json({ accepted: false }, { status: 503 })
if (request.headers.get('authorization') !== `Bearer ${secret}`) {
return Response.json({ accepted: false }, { status: 401 })
}
const payload = await request.json().catch(() => null)
const slug = payload?.slug
if (typeof slug !== 'string' || !/^[a-z0-9-]{1,80}$/.test(slug)) {
return Response.json({ accepted: false }, { status: 400 })
}
revalidatePath(`/catalog/${slug}`)
revalidatePath('/catalog')
return Response.json({ accepted: true }, { status: 202 })
}Секрет хранится на сервере и в настройке отправителя, а не в публичном NEXT_PUBLIC_ поле. Пример не ограничивает размер входного тела и частоту запросов: эти условия необходимо добавить на уровне обработчика или инфраструктуры перед production. Не позволяйте внешнему payload передавать произвольный путь, чтобы любой запрос не мог обновлять любой раздел сайта.
Разделите принятие события и появление новых данных
При вызове revalidatePath из Route Handler Next.js помечает путь для обновления при следующем посещении. Ответ 202 нашего обработчика означает принятие события, а не подтверждение уже готового нового HTML. Это существенное различие для интерфейса CMS и журналов. Поведение revalidatePath.
Поэтому проверка состоит из двух отдельных шагов. Сначала убедитесь, что CMS получила успешный ответ на правильный endpoint. Затем запросите изменённую карточку и найдите новое название в полученном документе. Если второй шаг не выполнен, в отчёте нельзя писать «обновление страницы подтверждено».
Одна запись может отображаться в нескольких местах: карточке, категории, поиске и подборке. Наш пример обновляет карточку и главную страницу каталога. Он не обновляет автоматически все остальные места использования. Опишите связанные маршруты; при общей модели данных можно рассмотреть обновление по тегам кеша, проверив механизм для своей версии Next.js.
Испытайте обычное событие и ошибки доставки
Подготовьте одну тестовую карточку на стенде. Сохраните исходное название, затем измените его на отличающееся контрольное значение. Отправьте событие с правильной авторизацией и после принятия запросите страницу заново. В CMS, API и HTML должно появиться одно согласованное значение.
Затем выполните негативные проверки. Запрос без секрета не должен приниматься. Повреждённый JSON и неподходящий slug не должны помечать маршруты. Повтор одного допустимого события должен сохранять корректный результат. Если событие пришло до публикации новой записи, повторите проверку после публикации и уточните порядок событий CMS.
Проверьте, как отправитель показывает неуспешную доставку и делает повтор. Если повторов нет, периодическое обновление через заданный интервал может уменьшить длительность устаревания, но не является доказательством доставки. У команды должен быть способ увидеть зависшее событие, а не только общий зелёный статус настройки webhook.
Если исходные данные новые, а внешний документ всё ещё старый, сравните ответ приложения и ответ через CDN. В задачу добавьте найденный слой кеша. Не очищайте все кеши по очереди без фиксации результата: это может скрыть причину до следующего изменения товара.
Что проверять после публикации исправления
Приёмку оформите на конкретной записи: старое значение, новое значение, время сохранения, идентификатор события, ответ обработчика и итоговый HTML. Отдельно отметьте, какие связанные страницы обновились. Если поменялся адрес товара, одного обновления кеша недостаточно: нужны новые ссылки, canonical и решение для старого URL.
Проверьте также ошибку источника данных. После webhook приложение не должно записывать повреждённый ответ CMS как успешную карточку. Для этого пригодится разбор ошибок API. Удаление товара, изменение цены и изменение заголовка — разные сценарии; тест только названия не подтверждает все три.
Сохраните технический аудит обновлённой страницы после ручного подтверждения. Он покажет доступные результаты на дату запуска, но не проверит подпись webhook и историю его доставки. Общий контекст кеширования и маршрутов есть в руководстве по Next.js 15.
Наблюдайте следующий обычный редакторский цикл без ручной очистки кеша. Если страница обновилась после настоящего сохранения и нового запроса, механизм прошёл полезную проверку. Поисковый робот при этом должен ещё повторно посетить URL: изменение HTML и обновление сведений в поиске происходят не одновременно.