Skip to content

Partial Prerendering (PPR) в продакшне: архитектурные паттерны (2026)

Partial Prerendering разрешает десятилетнюю дилемму между быстрой статической доставкой и серверной персонализацией. Страница декомпозируется на уровне Suspense-границ - CDN-обслуживаемые статические shells с TTFB < 50ms на маршрутах с динамическим, пользовательским и персонализированным контентом.

Архитектура Next.js Partial Prerendering - статический shell и динамические Suspense holes
Опубликовано:Обновлено:Время чтения:15 мин

Next.js Partial Prerendering (PPR), экспериментальный с Next.js 14 и инкрементально production-стабильный в Next.js 15, решает фундаментальный рендеринговый компромисс React-архитектуры: либо быстрый TTFB со статической генерацией (без серверной персонализации), либо корректная персонализация с медленным TTFB от SSR. Механизм PPR - пре-генерация статического HTML shell при сборке и стриминг динамического контента в Suspense-границы при запросе - это не улучшение ни одного из подходов, а другая модель. В этой статье разбираю: как работает двухфазный механизм ответа, как правильно архитектурить Suspense-границы, как PPR взаимодействует с Full Route Cache, и что показывают реальные числа в продакшне.

Часть I - Компромисс рендеринга, который разрешает PPR

  • ISR (вся страница статична): CDN отвечает за < 50ms TTFB. Но цена может быть неверной для региона пользователя, а инвентарь - устаревшим. Необходимы client-side fetches (useEffect → fetch → setState), повторно вводящие waterfall-паттерн.
  • SSR (вся страница динамична): Показывает корректные данные. Но TTFB - 300–800ms (время разрешения всех upstream-зависимостей). TTFB напрямую задерживает LCP: браузер не может нарисовать LCP-элемент до получения первого байта.
  • Streaming SSR (без PPR): React может стримить HTML-чанки по мере разрешения данных. Но без PPR весь маршрут по-прежнему динамический - первый байт всё равно результат работы streaming renderer на сервере, не CDN-кэш.
  • PPR (статический shell + dynamic holes): Статический контент страницы пре-рендерится при сборке и кэшируется на CDN edge. TTFB < 50ms. Затем открывается стриминговое соединение с динамическим рендерером, который заполняет Suspense-границы по мере разрешения данных. LCP-элемент (герой продукта) - в статическом shell и загружается со скоростью CDN.

Часть II - Механизм двухфазного ответа

PPR разбивает рендеринг маршрута на два этапа: build-time фаза генерирует статический HTML shell (все динамические контексты suspended → Suspense fallbacks), request-time фаза стримит динамический контент в этот shell.

  • Chunked transfer encoding: HTTP chunked transfer encoding (RFC 7230) позволяет серверу отправлять ответ по частям без знания общего размера. CDN отправляет статический shell как первый чанк немедленно. Динамический рендерер добавляет последующие чанки (<script> теги с RSC payload для каждой разрешённой Suspense-границы) в тот же HTTP-ответ по мере готовности.
  • Артефакт статического shell: Содержит полный HTML-документ: <html>, <head> со всеми метаданными и <link rel='preload'> хинтами, полное статическое RSC-дерево, Suspense fallback HTML для каждой динамической границы. Браузер получает preload хинты для LCP-изображения в первом чанке.
  • Изоляция динамического рендерера: Динамическая часть PPR-маршрута деплоится как отдельный serverless function invocation, выполняющий только Suspense-обёрнутые поддеревья.

Часть III - Архитектура Suspense-границ

  • Что форсирует динамический рендеринг (должно быть внутри Suspense): cookies(), headers(), searchParams, fetch() с cache: 'no-store', любые данные, требующие request-специфического контекста (ID пользователя из сессии, регион из cookie).
  • Что безопасно статично (должно быть вне Suspense): Заголовок продукта, описание, изображения, навигация, footer, schema.org, breadcrumbs, метаданные страницы.
  • Анти-паттерн: Обернуть весь <main> в одну Suspense-границу - функционально эквивалентно full SSR со skeleton loader. Правильная гранулярность: одна Suspense-граница на *каждую независимую динамическую задачу*. Независимые границы стримятся параллельно.
  • `searchParams` - самая частая PPR-ловушка: Компонент в дереве статического shell, обращающийся к searchParams, переводит весь маршрут в динамический. Решение: перенести всё потребление searchParams в Suspense-обёрнутые дочерние компоненты.

Часть IV - Конфигурация и кейсы применения

  • Инкрементальный режим (рекомендуется для продакшна): В next.config.ts: experimental: { ppr: 'incremental' }. Per-route: export const experimental_ppr = true; в page.tsx или layout.tsx.
  • eCommerce PDP (наибольший эффект): Статично: заголовок, изображение, описание, характеристики. Динамично (Suspense): цена, инвентарь, Add to Cart, рекомендации. LCP-улучшение: 1.8–2.4s (SSR) → 0.6–1.0s (PPR). Подробная архитектура - в гайде по headless Shopify.
  • Маркетинговые страницы с A/B тестами: Статично: весь маркетинговый контент. Динамично: назначение A/B варианта из cookie. Без PPR: любое чтение cookie форсирует SSR всей страницы.
  • Аутентифицированные дашборды: Статично: структура навигации, sidebar. Динамично: имя пользователя, нотификации, лента активности.
  • Блог: Статично: весь контент статьи (~95% страницы). Динамично: статус авторизации, персонализированные CTA.

Часть V - Дизайн fallback, Full Route Cache и результаты

  • Нулевой CLS: Suspense fallback-компоненты должны иметь явные размеры, соответствующие реальному контенту. Skeleton UI с explicit dimensions: CLS = 0. Плохо спроектированные fallbacks (display: none → 200px блок): CLS > 0.1.
  • PPR восстанавливает Full Route Cache для персонализированных маршрутов: cookies() внутри Suspense-границы не загрязняет кэшируемость всего маршрута. Статический shell хранится в Full Route Cache / CDN-кэше вне зависимости от содержимого Suspense-границ.
  • Измеренные результаты: TTFB: full SSR 300–800ms p75 → PPR CDN shell 20–80ms p75 (улучшение 4–10×). LCP на PDP: 1.8–2.4s → 0.6–1.2s. Детальная методология измерения LCP - в The Universal Web Performance Architecture.

Часть VI - Фреймворк принятия решений и ограничения

  • Используйте PPR когда: LCP-элемент в статическом контенте; динамический контент изолируем в Suspense-границах; текущий TTFB > 300ms из-за upstream-зависимостей; CWV показывают LCP > 2.5s p75.
  • Не используйте PPR когда: Вся видимая часть страницы динамическая; searchParams управляет всем лейаутом (страница результатов поиска); платформа не поддерживает CDN+streaming архитектуру PPR.
  • Известные ограничения: searchParams в дереве статического shell - переводит весь маршрут в динамический; Middleware, мутирующий заголовки ответа, может неожиданно взаимодействовать с PPR; cold start latency динамического рендерера; инкрементальный режим production-стабилен, глобальный ppr: true - ещё experimental; ошибки в dynamic holes (API timeout) изолированы внутри Suspense-границы - статический shell остаётся виден.
  • Подробный анализ взаимодействия PPR с App Router rendering model - в гайде по App Router миграции.

Заключение

PPR - наиболее значимое изменение архитектуры рендеринга в Next.js со времени введения RSC. Он разрешает структурное ограничение, которое ни ISR, ни SSR, ни streaming SSR не устранили: невозможность раздавать CDN-кэшированный HTML с TTFB < 50ms на маршрутах с любым серверным динамическим контентом. Для аудита архитектуры PPR или Core Web Vitals-инженерии - сервис оптимизации производительности | кейсы | обсудить проект.

Источники

Похожие статьи

Архитектура веб-производительности: системный анализ 12 инженерных принципов (издание 2026)

Глубокий технический разбор веб-производительности 2026 года: физика сетей, конвейеры изображений, модели выполнения JavaScript, Critical Rendering Path, edge-вычисления, Core Web Vitals и продакшн RUM - с реальными бенчмарками и конкретными примерами реализации.

EngineeringArchitectureCore Web Vitals
Читать статью

React 19: useOptimistic, use(), Server Actions - механизм и архитектура (2026)

Детальный разбор пяти новых примитивов React 19: useOptimistic (конкурентная composition оптимистичного состояния с автоматическим rollback), use() (условное чтение Promise и Context), Server Actions (RPC-over-HTTP контракт и Progressive Enhancement), useActionState (стейт-threading паттерн), useFormStatus. Action-based mutation архитектура, которая заменяет Redux/Zustand для серверных мутаций.

React 19ArchitectureNext.js
Читать статью

Миграция на Next.js App Router с Pages Router: полный практический гайд (2026)

Практический гайд по миграции продакшн-приложений Next.js с Pages Router на App Router. Стоимостная модель гидратации RSC, механика модульного графа webpack и директивы 'use client', внутреннее устройство Data Cache, инкрементальная стратегия, разбор типичных ошибок и фреймворк принятия решений.

Next.jsArchitectureMigration
Читать статью