---
title: "14 — Caching and Revalidation — Storing Fetch Results, Refreshing Them"
uid: caching-revalidation
tags: ["roadmap:nextjs", "revalidation", "memoization", "react-cache", "caching", "nextjs"]
excerpt: "Caching stores fetch results so later requests are faster; revalidation refreshes them. Memoization and React cache sit alongside as deduplication tools — four distinct mechanisms."
date: 2026-08-13T03:28:00+0000
source: https://www.aveshina.my.id/en/blog/caching-revalidation
---

The source of most "why isn't my data updating?" confusion in the App Router turned out to be four mechanisms wearing similar names. The model that clicked: **caching is storing fetch results so subsequent requests are faster; revalidation is how and when those results get refreshed.** [1] On top of that, **memoization deduplicates within a single request, and React's cache function lets me memoize any function manually** [2][3]. The four are distinct mechanisms that compose.

## The four layers

Next.js's caching is best understood as four distinct layers, each with a different scope and lifetime:

- **Request Memoization** — deduplicates identical fetch calls within a single request pass. If two components call fetch('/api/x') during one render, only one network request happens. Lives in memory, gone when the request ends [2].
- **Data Cache** — stores fetch results across requests and across deployments, keyed by the fetch. This is the layer that makes SSR affordable — the second visitor gets the cached result.
- **Full Route Cache** — caches the statically-rendered HTML of routes themselves.
- **Router Cache** — a client-side cache of visited route segments, speeding up back/forward navigation in the browser.

```figure
<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Four caching layers as concentric scopes. Innermost: request memoization (per-render, dedupes identical fetches). Middle: data cache (across requests). Outer: full route cache (rendered HTML). Beside them: router cache (client-side). Arrows show revalidateTag/revalidatePath reaching in to bust data + route caches.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- concentric layers -->
    <rect x="80" y="40" width="320" height="220" rx="14" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="100" y="62" font-size="11" font-weight="700" fill="#422006">Full Route Cache (HTML)</text>

    <rect x="110" y="80" width="260" height="160" rx="12" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="130" y="100" font-size="11" font-weight="700" fill="#052e16">Data Cache (fetch results)</text>

    <rect x="140" y="120" width="200" height="100" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="160" y="140" font-size="11" font-weight="700" fill="#1e1b4b">Request Memoization</text>
    <text x="240" y="170" font-size="10" fill="#1e1b4b" text-anchor="middle">per-request dedup</text>
    <text x="240" y="190" font-size="9" font-style="italic" fill="#1e1b4b" text-anchor="middle">in memory, gone at request end</text>

    <!-- Router cache -->
    <rect x="450" y="120" width="200" height="100" rx="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="550" y="144" font-size="11" font-weight="700" fill="#500724" text-anchor="middle">Router Cache</text>
    <text x="550" y="164" font-size="10" fill="#500724" text-anchor="middle">client-side</text>
    <text x="550" y="184" font-size="9" font-style="italic" fill="#500724" text-anchor="middle">speeds up back/forward</text>

    <!-- revalidation arrows -->
    <path d="M540,260 C420,260 380,240 360,210" fill="none" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="4 3" marker-end="url(#xarrow)"/>
    <defs>
      <marker id="xarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
        <path d="M0,0 L10,5 L0,10 z" fill="#dc2626"/>
      </marker>
    </defs>
    <text x="450" y="280" font-size="10" fill="#dc2626" text-anchor="middle">revalidateTag / revalidatePath bust data + route caches</text>
  </g>
</svg>
```

## Memoization in fetch — request-scoped dedup

Memoization here is the optimization of caching a function call's result for calls with the same inputs [2]. For fetch with GET/HEAD methods, this is automatic — Next.js deduplicates identical requests within a single render pass. The first call hits the network; subsequent identical calls return the memoized result. This is purely request-scoped: it prevents redundant work *during one render*, not across requests.

## React cache — manual memoization for non-fetch functions

The React cache function is the manual version [3]. fetch GET/HEAD requests are memoized automatically, but other fetch methods, or calls to database clients, CMS SDKs, or GraphQL clients that don't natively memoize, need to be wrapped manually:

```
import { cache } from 'react';
import { db } from '@/lib/db';

// wrapped — the function runs once per request for the same id
export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});
```

cache memoizes the return value so calling getUser('42') twice in one request hits the function once [3]. This is the bridge that brings non-fetch data access under the same dedup model.

## Revalidating — refreshing the cache

Revalidation is the process of updating cached data [4]. Two flavors:

- **Time-based** — revalidate after a period (revalidate: 60 in a fetch options, or export const revalidate).
- **On-demand** — revalidate based on events using revalidatePath (bust a path) or revalidateTag (bust everything tagged with a specific tag).

On-demand revalidation is what makes ISR-style freshness work without rebuilding: a CMS webhook fires, the handler calls revalidateTag('posts'), and the next request regenerates the affected data. The tag-based approach scales better than path-based because one tag can cover many entries.

## Revalidation errors — graceful degradation

When revalidation fails — a network blip, a database that's down, a bug in the revalidation logic — Next.js doesn't serve a broken page [5]. The last successfully generated data keeps being served from the cache, and on the next request it retries the revalidation. The user sees the stale-but-correct version; the system heals when the underlying issue resolves. This graceful degradation is a real win for reliability — a flaky upstream doesn't 500 the whole site.

## How I use this

I treat caching as the default and revalidation as the lever. For CMS-driven content, I tag every fetch with a relevant tag (posts, projects) and bust on-demand via webhooks — the data stays cached until something changes, then refreshes. For data that changes on a predictable cadence, time-based revalidation. I wrap non-fetch data access (database calls, Prismic SDK) in React cache so it dedups like fetch does. And when something isn't updating, my first question is which layer holds the stale value — usually the Data Cache, busted with revalidateTag.

## References

[1] Vercel, "Caching and revalidating," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/getting-started/caching-and-revalidating](https://nextjs.org/docs/app/getting-started/caching-and-revalidating)

[2] Vercel, "Request memoization," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/guides/caching#request-memoization](https://nextjs.org/docs/app/guides/caching#request-memoization)

[3] Vercel, "React cache function," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/guides/caching#react-cache-function](https://nextjs.org/docs/app/guides/caching#react-cache-function)

[4] Vercel, "Revalidating," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/guides/caching#revalidating-1](https://nextjs.org/docs/app/guides/caching#revalidating-1)

[5] Vercel, "Error handling and revalidation," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/14/app/building-your-application/data-fetching/fetching-caching-and-revalidating](https://nextjs.org/docs/14/app/building-your-application/data-fetching/fetching-caching-and-revalidating)

[6] "Next.js 14 tutorial — Request memoization," YouTube, 2024. [Video]. Available: [https://www.youtube.com/watch?v=tcLe3Xi0fJE](https://www.youtube.com/watch?v=tcLe3Xi0fJE)

```quiz
Q: Request memoization deduplicates identical fetch calls…
- across requests, forever
- within a single request pass — it's gone when the request ends
correct: 1
explain: Request memoization is per-render: two components calling the same fetch in one request share one network call. The memo is discarded at request end.

Q: Why would you wrap a database query in React's cache() function?
- To cache it across deployments
- Because non-fetch data access (DB, CMS, GraphQL clients) isn't automatically memoized like fetch GET — cache() brings it under the same dedup model
correct: 1
explain: fetch GET/HEAD is memoized automatically. Other data access isn't. Wrapping in cache() memoizes the return value so identical calls in one request run the function once.

Q: What's the difference between revalidatePath and revalidateTag?
- Nothing — they're aliases
- revalidatePath busts one URL path; revalidateTag busts everything tagged with a given tag (scales better across many entries)
correct: 1
explain: Path-based is precise for one route. Tag-based groups many fetches under a label so a single revalidateTag call can refresh them all — ideal for CMS content keyed by type.

Q: What happens when on-demand revalidation fails (e.g., the database is down)?
- The site serves a 500 to all users
- Next.js keeps serving the last successfully cached data and retries revalidation on the next request
correct: 1
explain: Revalidation errors degrade gracefully: stale-but-correct data keeps being served, and the system retries on subsequent requests until the upstream recovers.

Q: Which caching layer lives on the client and speeds up back/forward navigation?
- Request memoization
- The Router Cache
correct: 1
explain: The Router Cache is client-side, storing visited route segments in the browser to speed up client-side navigation. Request memoization is server-side and per-render.
```
