How do you fetch data in Next.js with getStaticProps, getServerSideProps, and Server Components?
In the Pages Router: getStaticProps runs at build (or on-demand revalidation) for SSG — fast, cacheable, perfect for content that doesn't change per-request. getServerSideProps runs on every request for SSR — needed when the response depends on cookies, headers, or per-user data. In the App Router (Next 13+), both are replaced by Server Components with fetch() options (cache, next.revalidate) — same SSG/SSR/ISR trade-offs, different API.
Next.js gives you several places to fetch data, each with a different timing model. Picking the right one is mostly about when does the data become known and how often does it change.
Pages Router (legacy but still common)
getStaticProps — Static Site Generation (SSG)
Runs once at build time (or on-demand via ISR). Output is HTML + JSON, cached on CDN.
export async function getStaticProps() {
const posts = await fetchPosts();
return {
props: { posts },
revalidate: 60, // ISR: regenerate at most once per 60s
};
}
export default function Blog({ posts }) {
return <PostList posts={posts} />;
}Use when:
- Data is shared across users (blog posts, marketing pages, product catalog).
- Slight staleness is OK (
revalidatecontrols how stale). - You want max performance + CDN cacheability.
For dynamic routes, pair with getStaticPaths:
export async function getStaticPaths() {
const posts = await fetchPostSlugs();
return {
paths: posts.map(slug => ({ params: { slug } })),
fallback: 'blocking', // or 'true' for streamed fallback, false for 404
};
}getServerSideProps — Server-Side Rendering (SSR)
Runs on every request. No caching by default.
export async function getServerSideProps({ req, params }) {
const session = await getSession(req);
if (!session) return { redirect: { destination: '/login', permanent: false } };
const data = await fetchUserData(session.userId);
return { props: { data } };
}Use when:
- Response depends on cookies, headers, user identity, or anything per-request.
- Data must be 100% fresh on each load.
- You can't precompute the page (real-time prices, personalized dashboards).
Trade-off: every request hits your origin. Slower TTFB than SSG; CDN can't cache (unless you set Cache-Control + s-maxage explicitly).
Client-side fetching (useEffect / SWR / React Query)
For data that doesn't need to be in the initial HTML (after first paint):
const { data } = useSWR('/api/user', fetcher);App Router (Next 13+)
getStaticProps/getServerSideProps don't exist. Instead, Server Components fetch directly, and fetch options control caching:
// SSG: cache forever (default in 13/14)
const data = await fetch('https://api.example.com/posts');
// ISR: revalidate every 60s
const data = await fetch(url, { next: { revalidate: 60 } });
// SSR: never cache, run per-request
const data = await fetch(url, { cache: 'no-store' });
// Tag-based invalidation
const data = await fetch(url, { next: { tags: ['posts'] } });
// then later: revalidateTag('posts')Note: Next 15 reversed the default to cache: 'no-store' — opt into caching with { cache: 'force-cache' } or next: { revalidate }. Check the version you're on.
Choosing in App Router
| Need | API |
|---|---|
| Build-time static | fetch(url, { cache: 'force-cache' }) |
| ISR (stale-while-revalidate) | fetch(url, { next: { revalidate: 60 } }) |
| Per-request SSR | fetch(url, { cache: 'no-store' }) or use cookies/headers |
| Tag-based revalidation | next: { tags: [...] } + revalidateTag |
| Per-user data | Read cookies()/headers() in a Server Component |
Decision flow
- Is the data per-user / per-request? → SSR (
getServerSidePropsorcache: 'no-store'). - Can it be precomputed? → SSG.
- Mostly static but updates occasionally? → ISR (
revalidateorrevalidateTag). - Only needed after first paint? → Client fetch (SWR, React Query).
Common gotchas
getServerSidePropsdefeats CDN caching unless you setCache-Control: s-maxage=…in the response headers.getStaticPropsdoesn't have access toreq— no cookies, no headers. Move to SSR if you need them.- ISR's first request after
revalidateexpires gets the stale page; the next request gets the regenerated one (stale-while-revalidate semantics). - App Router's
fetchis deduped per request — multiple components requesting the same URL hit the network once.
Follow-up questions
- •How does ISR's stale-while-revalidate actually work under the hood?
- •What's the difference between revalidate and revalidateTag?
- •When would you reach for client-side fetching over SSR?
- •How do Server Components change the data-fetching model in App Router?
Common mistakes
- •Using getServerSideProps when SSG would work — losing CDN caching for no reason.
- •Forgetting that getStaticProps runs only at build (or revalidate) — putting per-user logic there breaks personalization.
- •Returning huge prop objects — serialized JSON ships in the initial HTML.
- •In App Router, reading cookies() in a function intended to be cached — implicitly opts out of caching.
- •Forgetting fallback strategy for dynamic SSG routes — uncached slugs 404.
- •Setting revalidate too low (1s) and overloading origin on cache misses.
Performance considerations
- •SSG > ISR > SSR for TTFB and CDN cacheability. SSR adds origin latency per request (typically 50–500ms on top of CDN). Pick SSR only when you genuinely need per-request data; everything else should be SSG/ISR. For App Router, Server Components add zero JS to the bundle — use them for data-heavy presentation; client components only for interactivity.
Edge cases
- •getStaticProps with `notFound: true` returns 404 — useful for missing items in SSG.
- •Redirects from getServerSideProps must include `permanent: boolean`.
- •On-demand revalidation (`res.revalidate(path)`) lets webhooks invalidate specific pages.
- •App Router's fetch dedup is per-request, not cross-request — server cache is separate (next.revalidate).
- •Edge runtime restricts Node APIs (fs, crypto subset) — pick runtime per route.
Real-world examples
- •Marketing pages, blogs, docs → SSG with long revalidate (or pure SSG).
- •Product detail pages → ISR with revalidateTag on inventory change.
- •User dashboards → SSR or client-fetch.
- •Search results → client-side SWR/React Query.