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?
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
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?
let currentRoom;export function openRoom(room) { currentRoom = room; }export async function getMessages() { 'use cache'; return read(`msgs-${currentRoom}`);}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?
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)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?
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?
export async function GET() { const r = await fetch(`${LEDGER}/balance`, { next: { tags: ['balance'] } }); return Response.json({ balance: await r.text() });}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?
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?
async function Feed({ roomId }) { const getFeed = async () => { 'use cache'; return read(`feed-${roomId}`); }; return <FeedList items={await getFeed()} />;}Go deeper
Pro tips
- 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. - Give every
'use cache'acacheLife. Without one, the implicitdefaultprofile 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 acacheLife. Without one, the implicitdefaultprofile 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. "fetchrequests,GETRoute Handlers, and client navigations are no longer cached by default"; "In Next 14, Route Handlers that used theGETHTTP 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. Setcache: '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"; "Therefresh()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,GETRoute 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.
Related lessons
Checked against the tools and docs as of 2026-10-09.