Skip to content

Caching in Next.js 16: use cache and tags

Next 16 stores only what you mark, keys it by its inputs, and the call after a write decides who sees the change first.

Predict first

Before you read: predict

ExamHall's exam editor shows exam 42's title from getExam(id), a function marked 'use cache' that calls cacheTag('exam-' + id) and reads the exam service. Each candidate's exam page shows the title through the same getExam. Admin Lina fixes a typo in the title and presses Save; the save action writes the new title to the exam service and then calls updateTag('exam-42'). As soon as Save returns, candidate Omar opens exam 42.

What do Lina and Omar see, and how many times was the exam service asked for the title, from Save until Omar's page arrived?

Pick one

The 30-second version

In Next 16 nothing is cached unless you opt in, usually with 'use cache'. A cached result is stored under a key made of the function, its arguments and the outer values it reads. After a write, updateTag lets the writer read her change at once; revalidateTag(tag, 'max') only marks it stale; refresh() re-renders uncached data only.

Next 16 stores only what you mark, keys it by its inputs, and the call after a write decides who sees the change first.

How it works

Step 1 of 10

Opt-in only. In Next 16 a server fetch or database read runs per request unless opted into caching, as does a GET handler making one.
In the kitchen
  • every tin is mixed to order and nothing is kept, unless the manager puts a colour on the stock list → nothing cached unless opted in (steps 1, 3)

Analogy: A DIY store's paint-mixing counter (paint retail)

At the paint counter In Next
every tin is mixed to order and nothing is kept, unless the manager puts a colour on the stock list nothing cached unless opted in (steps 1, 3)
the store's old rule: every colour anyone ordered stayed on the shelf Next 13 and 14 (step 2)
a shelf tin's label copies everything written on the mixing card the key is the arguments (step 4)
a detail the clerk copies from the customer's file onto the card a captured value (step 5)
a detail the clerk remembers from the last customer and never writes down a module-level variable (step 6)
the designer changes a formula and asks: "bin the old tins and mix me one now"; the shelf behind her then holds the new colour updateTag (step 8)
she only marks the shelf "old formula", and leaves with an old tin revalidateTag(tag, 'max') (step 9)
"redo my order": colours mixed to order are mixed again, shelf tins handed back as they were refresh() (step 10)

In real terms: a result is kept only where you mark it, it is found again only by identical inputs, and the writer reads her own write only if the action expires the entry.

Breaks down when: a clerk can notice that a card describes the same colour in other words. Next compares serialised inputs only, and its shelf is one server's memory that a new build empties.

Contrasting case: the browser's HTTP cache from net-http-caching-headers, which finds a stored response by its URL. A 'use cache' result is found by the function's inputs, whichever page calls it.

Examples, run for real

4.1 ExamHall EXH-3350: an upgrade that reads on every visit

After ExamHall moved from Next 14 to Next 16, the exam service's read rate rose sharply (EXH-3350). The exam page still read the exam with a plain fetch, which Next 14 had kept. MR !3650 moved the read into getExamTitle(id), marked 'use cache'. Four visits, to exams 42, 42, 43 and 42:

4.1 EXH-3350, plain fetch: visits to exams 42, 42, 43, 42 · reads of the exam service per visit 1, 1, 1, 1 · total 4
4.1 MR !3650, 'use cache': visits to exams 42, 42, 43, 42 · reads of the exam service per visit 1, 0, 1, 0 · total 2

The plain fetch read the service on every visit (step 1). The cached function read each exam once: a new argument is a new key, and a repeated one is served from memory (steps 4, 7).

4.2 Chat: the moderator renames a room

A chat room's page shows its name from getRoomName(id) ('use cache', tagged 'room-' + id) and its unread count from an uncached read. The moderator types a new name and presses Rename; the action writes the name, adds one unread notice and calls updateTag('room-7'):

4.2 before Rename the moderator's page shows "Design crit" · "Unread: 0"
4.2 right after Rename (updateTag('room-7')) it shows "Design crit (Thursdays)" · "Unread: 1" · reads during Rename: name 1, unread count 1

The action expired the name, so its re-render read the name once and showed it, with the unread count read again beside it (step 8).

Try it

Answer first, then check. Nothing is saved.

Question 1 of 6

A chat app's room page takes the room from the URL and lists its messages through this module:

The page calls openRoom(room), then await getMessages(). On a freshly started server, Ana opens room 1, then Ben opens room 2.

What does Ben's page list, and how many times is the message store read?

Code for this question
let currentRoom;export function openRoom(room) { currentRoom = room; }export async function getMessages() {  'use cache';  return read(`msgs-${currentRoom}`);}
Pick one

Question 2 of 6

A video service passes the whole query to a cached function, from two pages:

Marketing links add a ref parameter. On a freshly started server, four viewers open /browse?region=eu&ref=newsletter, /browse?region=eu&ref=banner, /?region=eu&ref=newsletter and /browse?region=eu&ref=newsletter, in that order.

How many times is the catalogue store read?

Code for this question
export async function getCatalogue(query) {  'use cache';  return read(`catalogue-${query.region}`);}// in the browse page, and in the home page's "Popular in your region" row:// const query = await searchParams; … await getCatalogue(query)
Pick one

Question 3 of 6

A box office's show page prints "Seats left: 12" from getSeatsLeft(), a 'use cache' function tagged 'show-9', and below it "Your tickets: 0" from an uncached read inside <Suspense>. The Book button's Server Action takes one seat in the ticket store, so the store now holds 11 seats left and 1 ticket for this fan, and then calls refresh(). A fan presses Book.

When the action has returned, what does the fan's page show?

Pick one

Question 4 of 6

A payments ledger's Next 16 app (Cache Components on) serves GET /api/balance:

The ledger holds €40 when the app is built and when the first client calls. A payment then makes it €25, and two more clients call. No code calls revalidateTag.

What do the three clients get, and how many of their calls read the ledger?

Code for this question
export async function GET() {  const r = await fetch(`${LEDGER}/balance`, { next: { tags: ['balance'] } });  return Response.json({ balance: await r.text() });}
Pick one

Question 5 of 6

A chat app's room page shows the room's name from getRoomName(id), a 'use cache' function tagged 'room-' + id, and the unread count from an uncached read. The rename form's Server Action writes the new name and then calls updateTag('room-7'). MR !3651 replaces that call with revalidateTag('room-7', 'max'), "so readers keep getting the cached name during a busy evening".

Which review comment is right?

Pick one

Question 6 of 6

In an interview you wrote a room's feed like this:

The page's URL carries the room and the viewer (?room=1&u=ana). The interviewer asks whether room 2 ever shows room 1's feed, since roomId is not an argument of getFeed. "Prove it. On a freshly started server, Ana opens room 1, Ana opens room 2, Ben opens room 1, Ana opens room 1 again. How many feed reads?" she adds.

Which answer survives that question?

Code for this question
async function Feed({ roomId }) {  const getFeed = async () => {    'use cache';    return read(`feed-${roomId}`);  };  return <FeedList items={await getFeed()} />;}
Pick one

Go deeper

Pro tips
  1. Count origin reads in a test, not page loads. Point the app at a local stand-in for the data source that logs each read, then assert the count after a fixed sequence of visits; a cache change shows up as a number. When: reviewing an MR that adds or moves 'use cache' (steps 4, 7). Verify.
  2. Give every 'use cache' a cacheLife. Without one, the implicit default profile applies, which may not match how stale the data may be. When: adding a cached function (step 7). Status: DOC (Next.js docs, Caching: "We recommend pairing every cache directive with a cacheLife. Without one, the implicit default profile applies."); not run.
Source

Valid as of 2026-10-09.

  • Next.js docs, use cache (named in the curriculum row): https://nextjs.org/docs/app/api-reference/directives/use-cache · next-use-cache.txt. The key: "Build ID", "Function ID", "Serializable arguments", and "When a cached function references variables from outer scopes, those variables are automatically captured and bound as arguments, making them part of the cache key"; entries "stored in-memory" on the server. Supports steps 4, 5, 7.
  • Next.js docs, cacheLife (named in the curriculum row): https://nextjs.org/docs/app/api-reference/functions/cacheLife · next-cache-life.txt. Profiles and lifetimes; background for step 7.
  • Next.js blog, Next.js 16 (named in the curriculum row): https://nextjs.org/blog/next-16 · next-16-blog.txt. "Unlike the implicit caching found in previous versions of the App Router, caching with Cache Components is entirely opt-in"; updateTag() "provides read-your-writes semantics, expiring and immediately reading fresh data within the same request"; refresh() "for refreshing uncached data only. It doesn't touch the cache at all". Supports steps 1, 8, 10.
  • Next.js blog, Next.js 15: https://nextjs.org/blog/next-15 · next-15-blog.txt. "fetch requests, GET Route Handlers, and client navigations are no longer cached by default"; "In Next 14, Route Handlers that used the GET HTTP method were cached by default". Supports step 2.
  • Next.js docs, fetch: https://nextjs.org/docs/app/api-reference/functions/fetch · next-fetch.txt. "Caching is opt-in. Set cache: 'force-cache' to cache any request". Supports step 3.
  • Next.js docs, updateTag, revalidateTag, refresh, Revalidating, Mutating data: next-update-tag.txt, next-revalidate-tag.txt, next-refresh.txt, next-revalidating.txt, next-mutating-data.txt. updateTag "is designed for read-your-own-writes scenarios"; revalidateTag "marks the tagged data as stale" and "can be called in Server Functions and Route Handlers"; "The refresh() function does not revalidate tagged data".
  • Next.js docs, Route Handlers: https://nextjs.org/docs/app/getting-started/route-handlers · next-route-handlers.txt. "When Cache Components is enabled, GET Route Handlers follow the same model as normal UI routes in your application. They run at request time by default, can be prerendered when they don't access uncached or runtime data". Supports step 1 and the line after the steps.
  • Next.js docs, Caching, cacheTag, Caching without Cache Components, Migrating to Cache Components: saved as background for steps 1–3 and pro tip 2.

Checked against the tools and docs as of 2026-10-09.