Static vs Dynamic Rendering
Static pages have no in-between state—content is ready when the user arrives. Dynamic pages need Suspense boundaries to show progress while data loads. Understanding what triggers each mode is essential for building fast, responsive UIs.
With cacheComponents: true in Next.js 16, rendering is dynamic by default. You opt into static/cached rendering explicitly with "use cache".
What Makes a Route Dynamic?
Any of these trigger dynamic rendering:
- Reading
searchParamsorparams - Calling
cookies()orheaders() - Data fetches without
"use cache"directive - Using
connection()to opt into dynamic rendering
Static Example: Cached Queries
When your data fetching function uses "use cache", the content can be pre-rendered:
// data/queries/post.ts
export const getPublishedPosts = cache(async () => {
'use cache';
cacheTag('posts');
return await prisma.post.findMany({ where: { published: true } });
});
// app/page.tsx - No Suspense needed for cached content
export default async function HomePage() {
const posts = await getPublishedPosts();
return <PostList posts={posts} />;
}The posts are cached—no loading state needed because data is ready.
Dynamic Example: URL Parameters
When your component reads searchParams, it becomes dynamic and needs Suspense:
export default function DashboardPage({ searchParams }) {
return (
<Suspense fallback={<PostListSkeleton />}>
<PostList searchParams={searchParams} />
</Suspense>
);
}Inside PostList, the filter and sort values come from the URL—content can't be pre-rendered.
Push Dynamic Access Deep
Maximize cacheable content by pushing dynamic data access as deep as possible:
// ❌ Entire page becomes dynamic
export default async function Page({ searchParams }) {
const { filter } = await searchParams;
const posts = await getPosts(filter);
return <Layout><Posts posts={posts} /></Layout>;
}
// ✅ Only PostList is dynamic; Layout stays static
export default function Page({ searchParams }) {
return (
<Layout>
<Suspense fallback={<PostListSkeleton />}>
<PostList searchParams={searchParams} />
</Suspense>
</Layout>
);
}The Layout renders immediately. PostList streams in with its skeleton.
Hybrid Pages
Most real pages mix static and dynamic content:
export default function DashboardPage({ searchParams }) {
return (
<>
{/* Static - renders immediately */}
<DashboardHeader />
{/* Dynamic - needs searchParams */}
<Suspense fallback={<PostListSkeleton />}>
<PostList searchParams={searchParams} />
</Suspense>
</>
);
}Users see the header instantly while posts stream in.