Next.js Partial Prerendering (PPR), експериментальний з Next.js 14 і інкрементально production-стабільний у Next.js 15, вирішує фундаментальний рендеринговий компроміс: або швидкий TTFB зі статичною генерацією (без серверної персоналізації), або коректна персоналізація з повільним TTFB від SSR. Механізм PPR - пре-генерація статичного HTML shell при збірці та стримінг динамічного контенту у Suspense-межі при запиті - це не поліпшення жодного з підходів, а інша модель. У цій статті розбираю: як працює двофазний механізм відповіді, як правильно архітектурити Suspense-межі, як PPR взаємодіє з Full Route Cache, і що показують реальні числа у продакшні.
Частина I - Компроміс рендерингу, який вирішує PPR
- ISR (уся сторінка статична): CDN відповідає за < 50ms TTFB. Але ціна може бути неправильною для регіону користувача. Необхідні client-side fetches, що повторно вводять waterfall-патерн.
- SSR (уся сторінка динамічна): Показує коректні дані. Але TTFB - 300–800ms, що безпосередньо затримує LCP.
- Streaming SSR (без PPR): Поліпшує TTFB відносно blocking SSR, але shell все одно генерується per-request, не подається з CDN-кешу.
- PPR: Статичний контент пре-рендериться при збірці і кешується на CDN edge (TTFB < 50ms). Потім стримінгове з'єднання з динамічним рендерером заповнює Suspense-межі по мере вирішення даних. LCP-елемент - у статичному shell, завантажується зі швидкістю CDN.
Частина II - Механізм двофазної відповіді
- Build-time фаза: Next.js рендерить маршрут з усіма динамічними контекстами у suspended стані -
cookies(),headers(),searchParamsу Suspense-межах викликають рендеринг fallback-контенту. Результат - повний HTML-документ зі Suspense fallbacks у динамічних регіонах. Зберігається в CDN-кеші. - Request-time фаза: CDN повертає закешований статичний shell (TTFB < 50ms). HTTP-з'єднання залишається відкритим (chunked transfer encoding). Динамічний рендерер виконує Suspense-обгорнуті компоненти з реальним контекстом запиту і стримить
<script>чанки - React RSC payload, що замінює кожен Suspense fallback реальним вмістом. - Preload хінти у статичному shell:
<link rel='preload'>теги у<head>shell спрацьовують негайно з першим HTML-байтом - fetch LCP-зображення починається ще під час отримання HTML.
Частина III - Архітектура Suspense-меж та конфігурація
- Що форсує динамічний рендеринг (має бути у Suspense):
cookies(),headers(),searchParams,fetch()зcache: 'no-store', будь-які дані, що потребують request-специфічного контексту. - Що безпечно статично (поза Suspense): Заголовок продукту, опис, зображення, навігація, footer, schema.org, breadcrumbs.
- `searchParams` - найчастіша PPR-пастка: Компонент у дереві статичного shell, що звертається до
searchParams, переводить весь маршрут у динамічний. Рішення: перенести все споживанняsearchParamsу Suspense-обгорнуті дочірні компоненти. - Конфігурація (Next.js 15):
experimental: { ppr: 'incremental' }+export const experimental_ppr = true;уpage.tsxдля per-route увімкнення.generateStaticParamsсумісний з PPR.
Частина IV - Кейси застосування та продуктивність
- eCommerce PDP (найбільший ефект): Статично: заголовок, зображення, опис. Динамічно (Suspense): ціна, інвентар, Add to Cart, рекомендації. LCP: 1.8–2.4s (SSR) → 0.6–1.2s (PPR). Архітектура commerce - у гайді з headless Shopify.
- Маркетинг з A/B тестами: Статично: весь маркетинговий контент. Динамічно: призначення A/B варіанта з cookie.
- TTFB: Full SSR 300–800ms p75 → PPR CDN shell 20–80ms p75 (покращення 4–10×). Детальна методологія вимірювання LCP - у The Universal Web Performance Architecture.
- CLS: Suspense fallback з явними розмірами: CLS = 0. Погано спроектовані fallbacks: CLS > 0.1. PPR вводить CLS-ризик, якого немає у full SSR - потрібен дисциплінований дизайн skeleton UI.
Частина V - Full Route Cache, фреймворк рішень та обмеження
- PPR відновлює Full Route Cache для персоналізованих маршрутів:
cookies()у Suspense-межі не забруднює кешованість усього маршруту. Статичний shell зберігається у CDN-кеші незалежно від вмісту Suspense-меж. - Використовуйте PPR коли: LCP-елемент у статичному контенті; динамічний контент ізольований у Suspense-межах; поточний TTFB > 300ms; CWV показують LCP > 2.5s p75.
- Відомі обмеження:
searchParamsу дереві статичного shell - переводить маршрут у динамічний; cold start latency динамічного рендерера; інкрементальний режим production-стабільний, глобальнийppr: true- ще experimental; помилки у dynamic holes ізольовані всередині Suspense-межі. - Детальний аналіз PPR у контексті App Router rendering model - у гайді з App Router міграції.
Висновок
PPR - найзначніша зміна архітектури рендерингу в Next.js з часу введення RSC. Він вирішує структурне обмеження, яке не усунули ані ISR, ані SSR, ані streaming SSR: неможливість роздавати CDN-кешований HTML з TTFB < 50ms на маршрутах з будь-яким серверним динамічним контентом. Для аудиту архітектури PPR або Core Web Vitals-інженерії - сервіс оптимізації продуктивності | кейси | обговорити проект.
Джерела
- Next.js Team. 'Partial Prerendering': https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering
- Vercel. 'Partial Prerendering with Next.js': https://vercel.com/blog/partial-prerendering-with-nextjs-creating-a-new-default-rendering-model
- Next.js Team. 'Next.js 15 Release Notes': https://nextjs.org/blog/next-15
- React Team. 'Suspense Reference': https://react.dev/reference/react/Suspense
- Google. 'Largest Contentful Paint': https://web.dev/articles/lcp
- Google. 'Cumulative Layout Shift': https://web.dev/articles/cls
