Next.js Partial Prerendering (PPR): making a landing page static and dynamic at once
Published on 31 August 2026 · 8 min read
Almost all of our technical articles push in the same direction: build the landing page ahead of time (SSG), serve it from a CDN, keep the server off the critical path. And yet a real landing page almost always has one or two elements that can't be frozen at build time — a promo banner whose offer depends on the current week, a countdown to a real closing date, a price shown in the visitor's currency. Until now, the choice was binary: either compute those blocks client-side after hydration (at the cost of a flash of default content), or render the entire page dynamically on the server (at the cost of the whole page's TTFB and LCP, for one block that actually needs it). Next.js's Partial Prerendering (PPR) offers a third path: a static shell served instantly, with a few precise holes filled in via streaming at request time.
The trade-off PPR is trying to solve
On LanderKit itself, the week's commercial offer is deterministic from the date (a six-week cycle computed client-side on mount, specifically to stay compatible with a fully static export — see the full logic in our article on SSG vs ISR for a price that changes). That's a pragmatic and perfectly defensible choice at the scale of a site like this one: no extra server infrastructure, a trivial computation (a date), a barely perceptible flash of content at React hydration. But once the logic gets more complex — a price that depends on IP geolocation, a remaining-seats counter that has to come from a real database, an A/B test variant decided server-side — client-side computation gets more fragile, and rendering the whole page dynamically for a single block becomes costly for overall performance.
How Partial Prerendering works
The principle relies on a distinction React already supports natively: a route's default render is static, except for whatever is explicitly wrapped in a <Suspense> boundary and uses a so-called "dynamic" API (cookies(), headers(), a server-read searchParams, a fetch with cache: "no-store"). At build time, Next.js prerenders everything static — including the outline of each Suspense block, in the form of its fallback — and produces a single, complete HTML response, servable from an edge cache exactly like a regular SSG page. At request time, the server first returns that already-ready shell, then streams in the real content of each dynamic hole as soon as it's computed, without ever blocking the initial display while waiting for it.
Why TTFB and LCP don't take a hit
Nina Bhatti, Anna Bouch, and Allan Kuchinsky, in a study published in 2000 in Computer Networks on perceived tolerance to latency on the Web (available on Google Scholar), show that this tolerance isn't uniform across a single page: it depends on what the visitor expects from each visual zone at the moment they're looking at it. A visitor who sees the headline, the main image, and the call-to-action button appear instantly generally doesn't notice that a small secondary block — a stock counter, a localized price note — fills in a fraction of a second later. That's exactly what PPR enables: the LCP, almost always measured on the hero headline or image, stays that of a purely static page, since that content is part of the prerendered shell. Only the explicitly dynamic block waits for its data, while the rest of the page is already fully visible and interactive.
Concrete use cases on a landing page
- A price localized or converted into the visitor's currency: server-side IP geolocation determines the currency inside a single
Suspensecomponent, without making the rest of the page dynamic — see our article on local currency and displayed price. - A countdown to a real, server-stored deadline: useful for a webinar signup (demo) whose date genuinely closes, rather than a counter recomputed client-side on every render.
- An A/B test variant decided server-side: a natural complement to the "feature flags at the edge" approach described in our guide to A/B testing a static page — the flag is read inside the dynamic hole, while the rest of the page keeps the benefits of prerendering.
- A remaining seats or stock counter: data that changes more often than the page's own content would justify through a classic ISR revalidation.
The limits to know before adopting it
- Experimental status on Next.js 15. Full PPR is still marked experimental and subject to change in the official documentation; only the "incremental" mode (opting in route by route via
experimental_ppr = trueon the relevant segment, after settingexperimental.ppr: "incremental"innext.config) is considered usable in production. Next.js 16 stabilizes it under the name Cache Components, enabled by default in the App Router. - Incompatible with a fully static export. PPR assumes a server capable of streaming a response at request time (Node.js or edge runtime, typically on Vercel); a site using
output: "export"to serve only static files can't benefit from it. - Every dynamic hole needs a real fallback. A
Suspensewhose fallback doesn't exactly match the final content's size reintroduces the very layout shift PPR is meant to avoid — see our article on Core Web Vitals CLS for building a skeleton that doesn't move when the real content swaps in. - It's not a substitute for ISR. PPR answers content that must be computed on every single request; content that changes occasionally (a price updated once a day, for instance) is still better served by scheduled revalidation, which costs far less server resource per visit.
Should you adopt it today?
The call mostly depends on how many blocks on the page are genuinely dynamic. For a single simple element — like the week's offer computed from a date, similar to how LanderKit currently works — a client component mounted after hydration remains a reasonable option with no extra infrastructure. PPR earns its keep once that block depends on data that can't be reliably recomputed client-side (server-side geolocation, a database counter, a server-assigned A/B test flag), or once there's more than one such block on the same page. Since it's still an experimental feature on Next.js 15, it's worth testing first on a secondary route before turning it on for the page that actually carries the conversion, and tracking its move to stable in Next.js 16 before wider adoption. Since every LanderKit template ships as its own standalone Next.js mini-project, nothing stops you from adding this kind of dynamic block on a case-by-case basis, without touching the rest of the page or its load time.
FAQ
Frequently asked questions
What is Partial Prerendering in one sentence?
It's a Next.js technique that serves an entirely static page shell in a single response, while streaming in the content of a few explicitly dynamic zones (wrapped in a Suspense component) at request time.
Does Partial Prerendering replace ISR?
No, the two answer different needs. ISR serves content that changes occasionally by regenerating it on a scheduled revalidation interval. PPR serves content that must be recomputed for each individual request (a geolocation, an A/B test flag), which ISR can't do since it shares the same regenerated page across all visitors.
Can PPR be used with a static export (output: export)?
No. PPR assumes a server capable of streaming the response at request time (Node.js or edge runtime). A site exported as pure static files has no runtime server available and so can't fill in the dynamic holes.
Should you turn it on today on Next.js 15?
Only knowingly: full PPR is still marked experimental in Next.js 15's official documentation, with only the route-by-route incremental mode considered production-usable. Next.js 16 stabilizes it under the name Cache Components — a switch worth considering more broadly from that version on.
Read next
Related articles
- Adapting a landing page to the time and day: what research says about attention and decisions11pm, a visitor searches for a plumber after finding a leak when they got home. 11am on a Tuesday, a procurement manager compares three SaaS tools before lunch. Both land, hours apart, on a landing page that shows the exact same seven-field form — as if the time of day had no bearing on what the visitor can actually do with the page at that moment.
- The View Transitions API on a Next.js landing page: useful fade or gimmick?Click a template card and the next page snaps into place with no visual thread connecting the image you just saw to the one that replaces it. The browser's View Transitions API, now controllable from Next.js without a third-party library, lets one visual element persist from one page to the next. Two studies on perceived waiting time and animation help explain where that fade actually helps, and where it's just a gimmick that slows the page down for nothing.
- SSG or ISR: how to handle a price or offer that changes on a Next.js landing pageA statically generated (SSG) landing page is the fastest to serve: the HTML is already built, no server-side computation on every visit. But what happens when the price or offer it displays changes faster than your rebuilds? A concrete comparison of pure SSG, ISR (Incremental Static Regeneration) and client-side computation, including the approach LanderKit uses on its own site.