Суть Читайте реальные сниппеты React/Next — источник hydration mismatch, гонку SSR-фетча, конфиг ISR revalidate и расстановку Suspense — и выбирайте исправление с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги стратегий рендеринга читаются в коде и конфигах, а не в документации. Прочитайте каждый сниппет, предскажите его поведение в рантайме и выберите исправление, которое senior-инженер сделает первым.
Цель
Отработайте цикл, который проходит каждый инцидент со стратегией рендеринга: прочитать компонент или конфиг, предсказать поведение hydration, свежести или streaming и взяться за структурное исправление — а не за заплатку поверх симптома.
Сниппет 1 — мигающее приветствие
function Greeting() { // читается во время рендера, на сервере И на клиенте const hour = new Date().getHours(); const msg = hour < 12 ? 'Good morning' : 'Good evening'; return <h1>{msg}</h1>;}
Викторина
Completed
Этот компонент выбрасывает 'Text content did not match' при hydration и поднимает CLS. Где баг и каково верное исправление?
Heads-up Тип элемента нерелевантен — диф в текстовом контенте, вызванном чтением живого значения во время рендера. Смена тега ничего не даёт.
Heads-up getHours() в порядке; UTC лишь замаскировал бы половину проблемы с таймзоной. Разница в моменте рендера остаётся, поэтому mismatch сохраняется. Лечение — вообще не читать время во время рендера.
Heads-up Suspense стримит асинхронные данные; он не делает синхронное чтение Date детерминированным. Два рендера всё равно расходятся. Вместо этого отложите значение в эффект.
Страница рендерится на сервере, но этот компонент всегда печатает 'Loading…' в начальном HTML, а реальное имя появляется только после hydration. Почему и какой подход даёт больший рычаг?
Heads-up URL в порядке — данные приходят, просто после hydration. Проблема в том, что useEffect не может выполниться во время SSR, поэтому серверный HTML никогда не включает загруженное имя.
Heads-up Это лишь меняет текст плейсхолдера; сервер всё равно не может фетчить в эффекте. Реальное исправление — фетч на сервере, чтобы имя было в начальном HTML.
Heads-up 'use server' помечает Server Actions, а не способ запускать useEffect на сервере — эффекты никогда не выполняются во время SSR. Фетчите в Server Component или загрузчике и передавайте результат вниз.
Сниппет 3 — конфиг ISR
// Сегмент маршрута Next.js App Routerexport const revalidate = 3600; // секундыasync function ProductPage({ params }) { const product = await getProduct(params.id); // кэшируется, ревалидируется раз в час return <ProductView product={product} />;}
Викторина
Completed
Цены меняются непредсказуемо, иногда с интервалом в минуты, но `revalidate = 3600` означает, что цена может быть устаревшей до часа. Как сохранить выигрыш ISR в статической доставке, ограничив устаревание секундами?
Heads-up revalidate = 1 молотит регенерацию в каждом окне запроса и всё равно отдаёт устаревшие данные между непредсказуемыми изменениями цены. On-demand ревалидация обновляет ровно тогда, когда цена реально меняется.
Heads-up SSR платит серверной вычислительной ценой на каждый запрос для контента, одинакового у всех, теряя выигрыш CDN-кэша ISR. On-demand ревалидация сохраняет статическую доставку и ограничивает устаревание секундами.
Heads-up revalidate = 0 полностью выводит маршрут из кэширования — фактически SSR — отбрасывая ту самую выгоду статической доставки, которую вы пытаетесь сохранить.
При streaming SSR (renderToPipeableStream) чего достигает такая расстановка Suspense и что было бы, если убрать границу?
Heads-up С renderToPipeableStream Suspense-границы — единица streaming на сервере: оболочка сбрасывается сразу, и каждая граница стримится по мере разрешения её данных.
Heads-up Расстановка границы не переупорядочивает рендеринг; Stats быстрый и сбрасывается с оболочкой. Граница позволяет медленной части стримиться позже, не блокируя быструю.
Heads-up Это происходит БЕЗ границы. Граница делает обратное — изолирует медленный запрос, чтобы остальное стримилось сразу.
Итог
Четыре паттерна чтения покрывают большинство инцидентов со стратегиями рендеринга: живое значение (new Date(), Math.random(), window), прочитанное во время рендера, — это hydration mismatch, отложите его в useEffect; useEffect никогда не выполняется во время SSR, поэтому данные надо фетчить на сервере и передавать вниз (или встраивать в dehydrated cache), чтобы они появились в начальном HTML; временной ISR ограничивает устаревание слабо, но on-demand ревалидация через вебхук сужает его до секунд, сохраняя доставку с CDN; а Suspense-граница вокруг медленного запроса — это то, что позволяет streaming SSR сбросить оболочку первой вместо блокировки на самой медленной части. Диагностируйте по коду и конфигу, исправляйте структуру, затем проверяйте TTFB, INP и число mismatch.