Next.js 16 Partial Prerendering (PPR): Praktický sprievodca s príkladmi (2026)
Partial Prerendering v Next.js 16 je hybridná stratégia renderovania. Sprievodca aktiváciou PPR, Suspense hranicami a kombináciou s use cache pre okamžitý TTFB s personalizáciou.
Partial Prerendering (PPR) v Next.js 16 je hybridná stratégia renderovania, ktorá kombinuje statickú HTML škrupinu s dynamickými „dierami" streamovanými na požiadanie cez React Suspense. Výsledkom je okamžitý Time To First Byte ako pri statickom HTML, kým sa personalizovaný obsah (košík, používateľ, ceny) doťahuje paralelne. V tomto sprievodcovi vám ukážem, ako PPR zapnúť, kedy ho použiť namiesto ISR alebo SSR, ako navrhnúť Suspense hranice a ako odladiť bežné chyby, ktoré som si pri migrácii dvoch e-shopov odbil na vlastnej koži.
Partial Prerendering je v Next.js 16 stabilný (GA) a aktivuje sa cez experimental.ppr alebo per-route export const experimental_ppr = true.
PPR vytvorí počas buildu statickú škrupinu a všetko, čo je v <Suspense>, sa renderuje dynamicky pri requeste.
Oproti SSR znižuje TTFB o 200 až 600 ms, oproti ISR pridáva personalizáciu bez full revalidácie.
Vyžaduje React 19, App Router a Node.js runtime. Na Edge runtime PPR (zatiaľ) nefunguje v plnom rozsahu.
Najčastejšia chyba: zabudnutý <Suspense fallback> okolo dynamickej časti spôsobí pád celej stránky do SSR.
PPR sa kombinuje s direktívou 'use cache' a cacheLife pre granularnú kontrolu nad TTL.
Čo je Partial Prerendering v Next.js?
Partial Prerendering je renderovací režim, ktorý jednu URL adresu obslúži dvoma fázami. Počas buildu Next.js vyrenderuje statickú HTML škrupinu so všetkým, čo nezávisí od requestu (header, navigácia, layout, nepersonalizované sekcie). Pri požiadavke od používateľa server okamžite pošle túto škrupinu so streamovaným HTTP response a paralelne dorenderuje časti označené ako dynamické. Typicky ide o košík, prihláseného používateľa alebo geolokalizovaný obsah.
V Next.js 15 bol PPR len experimental. V Next.js 16 release notes Vercel oznámil, že PPR je teraz stabilný (GA), kompatibilný s React 19 a integruje sa s novým modelom kešovania 'use cache'. Pre vývojárov to znamená jediné: stránka má TTFB statického súboru, ale obsah personalizovaný ako pri SSR.
Pod kapotou používa PPR mechanizmus React Suspense + streaming SSR. Statická časť sa pošle hneď a dynamické komponenty obalené v <Suspense> sa serializujú do RSC streamu a vložia do DOM cez out-of-order streaming, akonáhle ich dáta dorazia. Prehliadač zobrazí fallback (skeleton) a po dotiahnutí ho v mieste nahradí finálnym HTML. Žiadna klientska navigácia, žiadny ďalší roundtrip.
Ako zapnúť PPR v Next.js 16
Aktivácia PPR v Next.js 16 prebieha v dvoch krokoch. Najprv povolíte flag v next.config.ts, potom označíte konkrétne routes, ktoré chcete PPR-ovať. Per-route opt-in odporúčam každému, kto migruje produkčný projekt. Postupne presuniete jednu stránku za druhou a sledujete metriky, namiesto toho, aby ste si celý web preklopili naraz.
Krok 1. Pridajte do next.config.ts flag:
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
ppr: 'incremental', // 'incremental' = opt-in per route
// ppr: true, // = enable for all routes
},
}
export default config
Krok 2. V konkrétnej routes (page.tsx alebo layout.tsx) exportujte flag:
Pri next build uvidíte v outpute značku ◐ (Partial Prerender) namiesto ● (SSG) alebo λ (SSR). To je vaše potvrdenie, že ruta naozaj beží v PPR režime. (Ak ju nevidíte, zvyčajne ste zabudli druhý krok.)
Praktický príklad: stránka produktu s PPR
Predstavte si typickú e-commerce stránku produktu. Obsahuje statický popis, obrázky a špecifikácie (rovnaké pre všetkých návštevníkov) a dynamický blok s live cenou, dostupnosťou skladu a počtom položiek v košíku používateľa. V klasickom SSR by celá stránka musela počkať na najpomalšiu DB query. S PPR pošleme všetko statické okamžite a dynamické dotiahneme cez stream. Takto to vyzerá v reálnom projekte:
Funkcia getProduct používa 'use cache' a beží len pri buildu (alebo pri revalidácii). Komponenty LivePrice a CartBadge čítajú cookies() alebo headers(), čo ich automaticky označí ako dynamické:
// app/products/[slug]/live-price.tsx
import { cookies } from 'next/headers'
import { fetchPriceFor } from '@/lib/pricing'
export async function LivePrice({ productId }: { productId: string }) {
const c = await cookies()
const region = c.get('region')?.value ?? 'EU'
const price = await fetchPriceFor(productId, region)
return (
<p className="price">
{price.amount} {price.currency}
{price.discount && <span>-{price.discount}%</span>}
</p>
)
}
Výsledok? Pri curl -I dostanete prvý bajt do zhruba 80 ms (statická škrupina z CDN), kým plná stránka s personalizovanou cenou dorazí o ďalších asi 250 ms. V Chrome DevTools to uvidíte ako dva chunky v Network taba response stream. Honestly, keď to človek vidí prvýkrát naživo, pôsobí to ako kúzlo.
PPR vs ISR vs SSR: kedy čo použiť
Rozhodnutie medzi PPR, Incremental Static Regeneration (ISR) a klasickým Server-Side Renderingom závisí od pomeru statického a dynamického obsahu na stránke. PPR je výhra vždy, keď je väčšina obsahu zdieľaná a len malá časť personalizovaná. Tabuľka nižšie zhŕňa, čo zvyčajne porovnávam pri review architektúry:
Vlastnosť
PPR
ISR
SSR
TTFB (typický)
50 až 100 ms
30 až 80 ms
200 až 800 ms
Personalizácia
Áno (per request)
Nie
Áno
Revalidácia
Per komponent
Per celá stránka
Každý request
CDN cache hit
Statická časť áno
Plne
Nie
Vyžaduje Node runtime
Áno
Áno
Edge alebo Node
Najlepší pre
Produkty, dashboardy s headerom
Blog, dokumentácia
Plne personalizované
Ak je stránka 100% statická (marketing landing page, blog post bez komentárov), PPR nepridá hodnotu. Použite plný SSG alebo ISR. Ak je 100% dynamická a vyžaduje autentikáciu pri každom byte (interný admin panel za authom), PPR neuškodí, ale benefit bude minimálny, lebo celá škrupina sa aj tak vyrenderuje s fallback skeletonmi. Sweet spot je verejne dostupná stránka s 1 až 2 dierami personalizácie.
Návrh Suspense hraníc a streamingu
Najdôležitejšie rozhodnutie pri PPR je kde nakresliť Suspense hranicu. Príliš veľká hranica (obalený celý layout) znamená, že väčšina stránky čaká na pomalý fetch. Príliš malá (každý <p> v Suspense) zahltí React reconciler a fragmentuje stream. V mojich projektoch sa osvedčilo jednoduché pravidlo: jedna Suspense hranica okolo jednej koherentnej business sekcie. Nič viac, nič menej.
Praktické pravidlá, ktoré sa mi opakovane vyplatili:
Jedna Suspense na logickú sekciu. Cena a dostupnosť patria spolu, takže patria do jednej hranice.
Fallback musí byť rovnako vysoký ako finálny obsah. Inak nastane layout shift a Core Web Vitals (konkrétne CLS) idú dolu. Skeleton box rovnakej výšky je minimum.
Nikdy neobaľujte Suspense okolo <Image>. Next.js Image má vlastný placeholder mechanizmus, ktorý by ste si len duplikovali.
Vnorené Suspense umožňujú progresívne odhaľovanie: vonkajšia hranica zobrazí kostru, vnútorné dorenderujú detail. React 19 ich streamuje out-of-order.
Server Actions vždy mimo statickú časť. Form, ktorý volá Server Action, musí byť v dynamickej diere. Inak Next.js nemôže škrupinu zacacheovať.
Riadenie poradia streamingu
React 19 streamuje Suspense bloky v poradí, v akom server dokončí ich renderovanie, nie v poradí, v akom sú v JSX. To je problém, ak chcete above-the-fold blok zobraziť skôr ako footer. Riešenie: v app/layout.tsx presuňte <Footer /> do vlastného <Suspense> s nižšou prioritou. Z pohľadu PPR je footer ďalšia diera, ktorá doteká až po hlavnom obsahu. Detaily o správaní Suspense API sú v oficiálnej React dokumentácii.
Kombinácia PPR s direktívou use cache
Next.js 16 priniesol direktívu 'use cache', ktorá nahrádza staré unstable_cache a integruje sa priamo do React Compiler-u. V kombinácii s PPR vzniká silný pattern: statická škrupina sa kešuje na hodiny, dynamické diery na sekundy. V poslednom projekte sme týmto setupom zhodili p95 latenciu produktovej stránky zo 720 ms na 110 ms, hoci sme stále mali per-user pricing.
// app/lib/products.ts
'use cache'
import { cacheLife } from 'next/cache'
export async function getProduct(slug: string) {
cacheLife('hours') // staticky popis - kesuj na 1h
const res = await fetch(`https://api.shop.com/products/${slug}`)
return res.json()
}
export async function getStock(id: string) {
'use cache'
cacheLife({ revalidate: 30 }) // sklad - revalidacia kazdych 30s
const res = await fetch(`https://api.shop.com/stock/${id}`)
return res.json()
}
Pre detail kešovacích profilov a custom cacheLife presetov si pozrite oficiálnu dokumentáciu cacheLife. PPR a 'use cache' nie sú alternatívy. Sú to dve vrstvy toho istého optimalizačného príbehu: čo kešovať rieši use cache, kedy to zobraziť rieši PPR.
Bežné chyby a ich riešenie
Pri migrácii produkčných projektov na PPR opakovane narážam na päť rovnakých problémov. Tu sú s riešením, ktoré som overoval v reálnom nasadení (a ktoré som väčšinou objavil tak, že som si rozbil staging).
„Route couldn't be statically generated"
Príčina: dynamic API (cookies(), headers(), searchParams) sa volá mimo Suspense hranice. PPR potom nemá ako oddeliť statickú časť od dynamickej a vráti routu do SSR. Riešenie: presuňte dynamic API volanie do komponentu, ktorý je obalený v <Suspense>.
Hydration mismatch po PPR aktivácii
Príčina: statická škrupina obsahuje new Date() alebo Math.random(), ktoré vracajú iné hodnoty pri buildu a pri request-e. Riešenie: presuňte tieto volania do dynamickej diery alebo použite suppressHydrationWarning pre stabilné prípady (časová zóna). Túto chybu som si odbil sám pri implementácii „posledná aktualizácia pred X minútami" widgetu.
Vysoký CLS po deploye
Príčina: Suspense fallback je menší než finálny obsah. Riešenie: skeleton musí mať deklarovanú minimálnu výšku CSS-kou (min-height: 4rem).
Nasadenie PPR na Vercel a vlastný hosting
Na Vercel funguje PPR bez konfigurácie. Platforma rozumie značke ◐ v build outpute a automaticky nasadí statickú škrupinu na edge CDN a dynamické funkcie na regionálne servery. Pri prvom request-e dorazí škrupina z najbližšieho POP-u, dynamické diery vyvolajú serverless funkciu v primárnom regióne.
Na vlastnom hostingu (Docker, Node.js za Nginx) PPR tiež funguje, ale stratíte CDN cache layer pre škrupinu. Riešenie: pred Next.js postavte Varnish alebo Cloudflare proxy, ktoré kešuje response stream do prvého HTTP/1.1 chunked markeru. Detaily nájdete v sekcii Self-hosting na Next.js deployment docs.
Pre projekty s vlastným reverse proxy je dôležitý správny middleware setup. Opísali sme ho v staršom článku o prechode z Edge na Node.js middleware.
Často kladené otázky
Funguje Partial Prerendering s App Routerom aj Pages Routerom?
Nie, PPR funguje výhradne s App Routerom v Next.js 13.4+. Pages Router používa starší model getStaticProps/getServerSideProps, ktorý nepodporuje streaming Suspense a teda ani PPR.
Aký je rozdiel medzi PPR a Streaming SSR?
Streaming SSR renderuje celú stránku pri každom requeste, len odosielanie ide po chunkoch. PPR má jednu trvalo statickú škrupinu vygenerovanú pri buildu a streamuje len dynamické diery. PPR má teda nižší TTFB a CDN cache hit pre väčšinu HTML.
Môžem PPR použiť s next/dynamic import?
Áno, ale samotný next/dynamic je pre client komponenty a beží na klientovi. PPR pracuje so server side renderingom. Pre dynamické server komponenty stačí <Suspense> okolo async komponentu.
Ako odladím, ktorá časť stránky je statická a ktorá dynamická?
Spustite next build a sledujte značky pri každej route: ● je SSG, ◐ je PPR, λ je SSR. V Chrome DevTools v Network paneli kliknite na HTML response a v sekcii Response uvidíte chunked stream. Prvý chunk je škrupina, ďalšie sú diery.
Vyžaduje PPR React 19 alebo stačí React 18?
Next.js 16 vyžaduje React 19 ako peer dependency a PPR využíva nové async transition API z React 19. Na React 18 PPR jednoducho nezapnete, buildscript zlyhá s chybou peerDeps.
Praktický sprievodca Server Actions v Next.js 16: ako písať formuláre s useActionState, validovať vstupy Zod-om, revalidovať cache a vyhnúť sa typickým bezpečnostným chybám v produkčnom kóde.
Kompletný sprievodca direktívou use cache v Next.js 16 s praktickými príkladmi. Kešujte komponenty, stránky a funkcie pomocou cacheLife, cacheTag a PPR.
Naučte sa používať Node.js runtime v Next.js middleware — od experimentálnej podpory v 15.2, cez stabilnú verziu v 15.5, až po nový proxy.ts v Next.js 16. S praktickými príkladmi autentifikácie a rate limitingu.