Static and dynamic rendering
Next.js can prepare a page's HTML once at build time — that is static rendering. Or it can render the page again for every visitor — that is dynamic rendering. A static page is served instantly, a dynamic one always shows current data. Next.js chooses the mode itself by looking at the page code.
How Next.js decides
A page becomes dynamic if it uses something known only at request time:
| What the page uses | Mode |
|---|---|
Only its own data and fetch | Static |
cookies() or headers() | Dynamic |
The searchParams prop | Dynamic |
export const dynamic = 'force-dynamic' | Dynamic |
A dynamic segment without generateStaticParams | Dynamic |
How to see the mode
Build the project:
npm run buildAt the end the terminal shows a table of routes. The ○ symbol means a static page, ƒ a dynamic one, ● static pages built from a list of parameters. Everything in this lesson applies to the regular Next.js 16 model; with Cache Components turned on the rules differ, and they get a separate lesson.
generateStaticParams
The post page app/posts/[slug]/page.tsx is dynamic by default: Next.js does not know the possible slug values in advance. The generateStaticParams function provides that list:
// app/posts/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { getPost, getPosts } from '@/lib/posts';
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return <h1>{post.title}</h1>;
}The function returns an array of objects, one per page; the key matches the folder name. After npm run build the table shows ● and the addresses /posts/hello-nextjs and /posts/server-components — their HTML is ready in advance.
New posts after the build
A post added after the build still opens: Next.js renders it on the first request and stores it. If such addresses should give a 404, add to the page file:
// app/posts/[slug]/page.tsx (fragment)
export const dynamicParams = false;A forced dynamic page
If a page must show fresh database data on every request, say so explicitly:
// app/posts/page.tsx (fragment)
export const dynamic = 'force-dynamic';Then /posts is no longer built in advance and gets the ƒ symbol in the build table. A softer option is export const revalidate = 60 from the caching lesson.
Common mistake
Reading the list of posts from a database on a page without these settings and wondering why new posts never appear on the server. A page without dynamic functions was built statically, and its HTML does not change. Choose explicitly: revalidate, dynamic = 'force-dynamic' or revalidatePath after the data changes.