Next.js 15 Partial Prerendering combines static shell generation with runtime content streaming in a single request. This rendering model allows developers to serve static shells instantly while streaming slower, runtime data components as they resolve. By combining these two approaches, applications achieve faster initial load times without sacrificing real-time data updates.
Implementing Next.js 15 Partial Prerendering requires a solid understanding of React Suspense boundaries. Instead of choosing between fully static or fully runtime pages, you can now mix both models on a single route. This approach changes how we build user interfaces, moving away from traditional server-side rendering bottlenecks.
The Architecture of Partial Prerendering
At its core, this technology splits a page into static and runtime parts during the build phase. The static shell contains the layout, navigation, and static text assets. When a user requests the page, the server immediately sends this pre-rendered shell to the browser.
While the browser renders the static shell, the server continues executing runtime components. These components fetch live data, such as user profiles or shopping cart items. As soon as the server resolves these data requests, it streams the HTML chunks to the client over the same connection.
React Suspense as the Boundary
React Suspense boundaries define the split points between static and runtime content. Any component wrapped in a Suspense boundary is treated as a runtime component. The fallback UI defined in the Suspense boundary is rendered as part of the static shell.
When the runtime component finishes loading, the fallback UI is replaced by the actual component. This mechanism ensures that slow database queries or external API calls do not block the initial page load. It provides a smooth user experience by showing content as soon as it becomes available.
Enabling PPR in Next.js 15
To use this feature in your production environment, you must enable it in your configuration file. Since the feature is currently experimental, you need to set the appropriate flags in your configuration. Open your configuration file and add the experimental object.
// next.config.js
const nextConfig = {
experimental: {
ppr: 'incremental',
},
};
module.exports = nextConfig;Using the incremental value allows you to adopt the feature gradually. You do not have to enable it for your entire application at once. Instead, you can opt-in on a route-by-route basis, which reduces risk during migration.
Route Configuration
Once the experimental flag is active, you can enable the feature on specific routes. You do this by exporting a configuration variable from your page file. This tells the compiler to apply the rendering model to this specific route.
// app/dashboard/page.tsx
export const experimental_ppr = true;
export default function DashboardPage() {
return (
<main>
<h1>Dashboard</h1>
<Suspense fallback={<CardSkeleton />}>
<RuntimeStats />
</Suspense>
</main>
);
}By setting this variable, the build process knows to pre-render the static parts of the dashboard. The runtime stats component will be streamed separately when the page is requested.
Designing Effective Fallback Components
Designing good fallback components is essential for a good user experience. Since the fallback UI is baked into the static shell, it must load instantly. Avoid using complex logic or external assets inside your fallback components.
Use simple CSS skeletons that match the layout of the final content. This prevents layout shifts when the runtime content replaces the fallback. Layout stability is a key factor in user satisfaction and search engine rankings.
Avoiding Layout Shifts
To prevent layout shifts, ensure your fallback components have the same dimensions as the final rendered components. If a runtime card is two hundred pixels tall, the skeleton fallback should also be two hundred pixels tall. This reserves the space on the screen.
Using fixed heights and flexible grid layouts helps maintain visual stability. You can use simple CSS animations, like a subtle pulse effect, to indicate that content is loading. Keep these animations lightweight to avoid consuming client CPU resources during page load.
Production Deployment Strategies
Deploying this setup to production requires a hosting provider that supports streaming responses. Most modern cloud platforms support HTTP streaming, but you should verify this before launching. Without streaming support, the benefits of the static shell are lost.
You should also monitor your server resources. Because the server keeps connections open to stream runtime components, it may handle more concurrent connections than before. Ensure your server configuration can handle this load.
Caching and Edge Networks
The static shell can be cached at the edge, close to your users. This means the initial HTML response arrives almost instantly, regardless of the user's location. The runtime parts are then fetched from your origin server or edge functions.
Configure your CDN to cache the static shell while passing the streaming chunks through. This hybrid approach reduces the load on your origin database. It also ensures that users get the fastest possible time to first byte.
Monitoring and Performance Metrics
After deploying, you must track your performance metrics to verify the benefits. Focus on Time to First Byte and First Contentful Paint. You should see a significant decrease in both metrics compared to traditional server rendering.
Use real user monitoring tools to collect data from actual visits. Look for improvements in Cumulative Layout Shift, which indicates how stable your page is during loading. Adjust your fallback components if you notice high layout shift scores.
Analyzing Server Logs
Check your server logs to ensure that streaming is working as expected. Look for long-running requests that might indicate slow runtime components. If a component takes too long to resolve, it can delay the final page completion.
Optimize your database queries and API calls within runtime components. Even though they do not block the initial shell, fast response times are still important for a good user experience. Use caching strategies for runtime data where appropriate.



