Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Back to overview
Core 16 min read

Understanding caching

Why the page is suddenly frozen after deploying

Builds on: Fetching data
In one sentence

Next.js caches in four places. Nearly every surprise comes from not knowing which one is currently at work.

This is the lesson most people get stuck on – and the reason Next.js has a reputation for being “unpredictable”. It is predictable, though, as soon as you can tell the four caches apart.

Careful You need a real build to try this out

In development almost nothing is cached. Anyone investigating caching has to use npm run build && npm run start – otherwise you simply cannot see the behaviour.

The four caches

overview.txt
1. Request memoization   during ONE page build
   The same fetch twice? It still only runs once.

2. Data cache            across requests and deploys
   fetch responses, until you declare them stale.

3. Full route cache      the finished HTML of static pages
   Produced at build time, served directly afterwards.

4. Router cache          in the user's browser
   Recently visited pages, so "back" is instant.
Put another way

Like a kitchen: the ingredient delivery does not arrive fresh for every dish (Data cacheNext.js remembers `fetch` responses across requests and deploys, until you declare them stale.). A dish that is always the same gets pre-cooked (Full route cacheThe finished HTML of a static page, produced at build time and served directly afterwards.). And what the guest just had on the table does not need plating again (Router cacheThe store in the browser: navigating back shows the page instantly from memory.).

The big one: static or dynamic

The largest effect comes from whether a page is produced once at build time (Static renderingThe page is produced at build time and then served identically to everyone. Fast and cheap – the default where possible.) or fresh on every request (Dynamic renderingThe page is produced fresh on every request. Necessary as soon as it depends on cookies, headers or search params.). Next.js decides that itself – based on what you use.

decision.txt
The page stays static as long as you touch none of these.

It turns dynamic as soon as you use:
  • cookies()
  • headers()
  • searchParams
  • fetch(..., { cache: "no-store" })
  • export const dynamic = "force-dynamic"
Pitfall A single cookies() is enough

These Dynamic functions`cookies()`, `headers()` and `searchParams`. Use one and the page can no longer be static. are contagious: as soon as one appears anywhere in the tree, the whole page can no longer be static. That is why you push them as deep as possible – or wrap them in <Suspense> so only that part goes dynamic.

How long does fetched data count as fresh?

fetch-options.tsx
// Default: cached until you declare it stale
await fetch(url);

// Never cache – every request goes out (makes the page dynamic)
await fetch(url, { cache: "no-store" });

// At most 60 seconds old
await fetch(url, { next: { revalidate: 60 } });

// With a tag, so you can invalidate it precisely later
await fetch(url, { next: { tags: ["posts"] } });
app/blog/page.tsx
// Applies to the whole page rather than per fetch:
export const revalidate = 3600;          // rebuild hourly
export const dynamic = "force-dynamic";  // always fresh
export const dynamic = "force-static";   // always static
These exports live in page.tsx or layout.tsx and apply to everything below.
Good to know The default has changed

In early versions of the App Router fetch was always cached, which surprised a lot of people. Since Next.js 15 the default is more conservative. If you are following an older guide, check its version – this is exactly the point where old and new examples contradict each other.

What you need when

Reach for it when …
  • +

    Static: marketing pages, blog, documentation, product catalogue

  • +

    revalidate: content that should be current but not to the second

  • +

    Dynamic: anything personal – account, cart, dashboard

Skip it when …
  • no-store as a reflex – that throws away the biggest advantage

  • force-dynamic on the whole page when only part of it is personal

  • Investigating caching questions in development mode

In more depth: debugging caching problems optional

When a page shows the wrong data, this order helps – from the outside in:

procedure.txt
1. Is the page static at all?
   npm run build tells you:
     ○  static       ← produced at build time
     ƒ  dynamic      ← on every request

2. Does a hard reload (Ctrl+Shift+R) show fresh data?
   → then it was the router cache in the browser.

3. Does a new build show fresh data?
   → then it was the full route cache: you need revalidate.

4. Still stale after that?
   → it is the data cache: revalidateTag or no-store.
Tip The build output is your best friend

npm run build lists every address with a symbol in front. If you see where you expected ƒ, you have found the cause without reading a line of code.

Does it stick?

5 questions on this lesson. Wrong answers show up in your stats.

Hirefullstack

Need React firepower on your team?

We have been building React and Next.js applications for clients across Germany for years – as a single expert, as reinforcement for an existing team, or as a complete Scrum team.

Talk about your project →