| name | nextjs-rendering |
| description | Provides expert guidance on Next.js rendering behavior, including static vs dynamic rendering, streaming, Suspense boundaries, cache components, and the prospective render system. Use when working with Next.js SSG, ISR, streaming, server components, or debugging rendering modes. |
Next.js Rendering
Expert guidance on Next.js rendering modes, streaming behavior, and static vs dynamic page generation.
Static vs Dynamic Rendering
Next.js has two fundamental rendering types: static and dynamic.
Static rendering:
- Pages are built at build time and uploaded to a CDN
- No compute occurs at request time
- Assets are replicated to edge locations globally
- Faster response times (no compute, cached near user)
- Remains available even if origin region goes down
Dynamic rendering:
- Pages are executed on-demand in a configured region
- Involves compute at request time
- Goes down if the region becomes unavailable
- Required when content cannot be determined at build time
SSG (Static Site Generation) and ISR (Incremental Static Regeneration) are the same underlying mechanism — the only difference is whether all paths are generated upfront or incrementally on-demand.
Determining Render Mode: Async Dynamic APIs
Next.js determines whether a page is static or dynamic using async dynamic APIs. The framework performs a prospective render to detect what resolves synchronously vs asynchronously.
The microtask test:
- Next.js runs an initial "warmup render" to search for cached instances and fill caches
- A second render runs for just one tick, then aborts
- Anything that resolves within that microtask is considered "instant" and can be static
- Anything that cannot resolve in one tick is treated as dynamic
Special case for params:
Routes using params can be static because each route is called once per item in generateStaticParams. Since the params are known at build time, they resolve instantly.
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }];
}
export default async function Page({ params }: { params: { id: string } }) {
}
Streaming and Suspense Boundaries
Streaming is only enabled for dynamic rendering. Static content does not stream because no compute is involved — the complete HTML is served from the CDN.
Document-level Suspense requirement:
To enable streaming with a loading skeleton, wrap the HTML document with an empty Suspense boundary. Without a document wrapper outside the boundary, there is nothing to stream — the framework needs the outer shell to send while inner content loads.
<Suspense fallback={null}>
<html>
<body>
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
</body>
</html>
</Suspense>
ISR and Suspense fallbacks:
Suspense boundary fallbacks do not display when generating ISR paths. Since ISR generation happens at build time or on first request (static generation), there is no streaming — the complete page is generated before being served.
Cache Components (use cache)
Cache components can combine static and dynamic rendering within the same page, but the page itself still has a render mode.
Applying use cache to a page component:
When use cache is added to a page or the main component of a page, the page produces static output that:
- Will be cached (static)
- Won't be streamed
- Won't have "holes" (Suspense boundaries) in it
Cache components with dynamic pages:
Even with use cache on individual components, the page overall is still either static or dynamic. Cache components allow granular control — static cached sections within dynamic pages, or dynamic sections within static pages.
'use cache';
export default async function Page() {
}
export default async function Page() {
return (
<>
<StaticCachedSection /> {/* has 'use cache' */}
<DynamicSection /> {/* no cache directive */}
</>
);
}
Rendering Workflow
When working with Next.js rendering:
- Determine if the page can be static (all data available at build time)
- If static, decide between full SSG (all paths) or ISR (incremental)
- If dynamic is required, plan Suspense boundaries for streaming
- Add document-level Suspense wrapper if streaming with skeletons
- Use
use cache granularly to cache expensive components within dynamic pages
- Remember: streaming only works for dynamic rendering, not static/ISR