Back to Next.js
Next.js
medium
mid

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.

8 min read·~5 min to think through

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.

tsx
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 (revalidate controls how stale).
  • You want max performance + CDN cacheability.

For dynamic routes, pair with getStaticPaths:

tsx
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.

tsx
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):

tsx
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:

tsx
// 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

NeedAPI
Build-time staticfetch(url, { cache: 'force-cache' })
ISR (stale-while-revalidate)fetch(url, { next: { revalidate: 60 } })
Per-request SSRfetch(url, { cache: 'no-store' }) or use cookies/headers
Tag-based revalidationnext: { tags: [...] } + revalidateTag
Per-user dataRead cookies()/headers() in a Server Component

Decision flow

  1. Is the data per-user / per-request? → SSR (getServerSideProps or cache: 'no-store').
  2. Can it be precomputed? → SSG.
  3. Mostly static but updates occasionally? → ISR (revalidate or revalidateTag).
  4. Only needed after first paint? → Client fetch (SWR, React Query).

Common gotchas

  • getServerSideProps defeats CDN caching unless you set Cache-Control: s-maxage=… in the response headers.
  • getStaticProps doesn't have access to req — no cookies, no headers. Move to SSR if you need them.
  • ISR's first request after revalidate expires gets the stale page; the next request gets the regenerated one (stale-while-revalidate semantics).
  • App Router's fetch is 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.

Senior engineer discussion

Seniors should pick the data-fetching strategy as part of the architecture — not per-page on a whim. They map URL → audience (shared vs personal) → freshness budget → caching strategy. They also know the cost: SSR is expensive at scale; ISR is the usual sweet spot. With App Router, they think in terms of Server Components vs Client Components and where the data lives (server-only secrets vs hydrated state).

Related questions