Data Fetching and Rendering Modes
Server Components fetching data directly, static/dynamic/ISR rendering modes, and when to use Client Components.
Server Components fetch data directly
In the App Router, every component is a Server Component by default — it renders on the server, and can be an async function that fetches data directly, with no useEffect, no loading state, and no client-side network waterfall:
// app/posts/page.tsx
interface Post {
id: number;
title: string;
}
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://api.example.com/posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
The fetch call, and the rendering it feeds, both happen on the server before any HTML reaches the browser — the client receives finished markup, not a spinner followed by a second network round-trip.
The rendering modes
Next.js decides, per route, when that rendering actually happens — conceptually there are three modes:
| Mode | When the HTML is generated | Typical use |
|---|---|---|
| Static (default) | Once, at build time, then reused for every request | Marketing pages, docs, blog posts that rarely change |
| Dynamic | Freshly, on every incoming request | Pages showing per-user or highly time-sensitive data (a dashboard, a cart) |
| Incremental Static Regeneration (ISR) | At build time, then automatically regenerated in the background after a set interval | Content that changes occasionally but doesn't need to be real-time (product listings, articles) |
A route becomes dynamic automatically as soon as it does something inherently per-request — reading cookies or headers, or fetching with { cache: 'no-store' }. Otherwise, Next defaults to static rendering. ISR is opted into with a revalidate value:
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 }, // regenerate this data at most once every 60 seconds
});
return res.json();
}
The first request after 60 seconds still gets the previously cached page instantly, while Next regenerates a fresh version in the background for subsequent requests — visitors are never blocked waiting on regeneration.
Client Components, and when you actually need one
Server Components can't use state, effects, browser-only APIs, or event handlers like onClick — for that you need a Client Component, opted into with a 'use client' directive at the very top of the file:
'use client';
import { useState } from 'react';
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? 'Liked' : 'Like'}
</button>
);
}
'use client' marks the boundary where a piece of the component tree switches from server-rendered to hydrated-in-the-browser; components imported below that boundary run on the client too, but a Client Component can still receive Server-rendered components as children from above it. Reach for a Client Component specifically when you need:
useState,useReducer, oruseEffect- Event handlers (
onClick,onChange, ...) - Browser-only APIs (
window,localStorage, ...) - A third-party library that itself depends on any of the above
Everything else — anything that's just displaying data — is better left as a (default) Server Component, since it ships zero JavaScript to the browser for that piece of the UI.
Common mistakes
- Adding
'use client'to components that don't actually need it "just in case" — every Client Component (and everything it imports) ships its JavaScript to the browser and loses the zero-JS, server-rendered benefit by default. - Trying to use
useStateoronClickinside a Server Component — it fails, since Server Components never run in the browser and have no interactivity model at all. - Fetching with the default caching behavior and expecting content to always be fresh — without
cache: 'no-store'or arevalidatevalue, a successfulfetchinside a statically rendered route is cached indefinitely.