AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 14 — Caching and Revalidation — Storing Fetch Results, Refreshing Them

14 — Caching and Revalidation — Storing Fetch Results, Refreshing Them

August 13, 20265 min read
Download as Markdown

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.
Full Route Cache (HTML) Data Cache (fetch results) Request Memoization per-request dedup in memory, gone at request end Router Cache client-side speeds up back/forward revalidateTag / revalidatePath bust data + route caches

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

[2] Vercel, "Request memoization," Next.js Docs, 2024. [Online]. Available: 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

[4] Vercel, "Revalidating," Next.js Docs, 2024. [Online]. Available: 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

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

Knowledge check · Question 1 of 5

Request memoization deduplicates identical fetch calls…

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!