We know data cache from the old caching model. Every time you use the fetch API and cache it (old model: { cache: no-store }), the response from the fetch is stored and saved in the Next build folder. We covered this in an earlier chapter on data cache.
In the Cache Components model, data cache still exists! The responses from using the fetch API with cache ("use cache" directive) are still stored in the same Next build folder (/.next/cache/fetch-cache).
Let me state this clearly: an async function that uses the fetch API and the "use cache" directive will generate 2 distinct caches layers at build time:
- The raw HTTP response from the fetch (
data cache) -> as files in the build folder. - The return value of the function -> in-memory.
Here is an example. We reuse our <Todo /> component and getTodo function from earlier chapters:
Note: the examples are available on GitHub.
// app\lib\getTodo.ts
type TodoT = {
id: number;
title: string;
completed: boolean;
};
export async function getTodo(id: number) {
'use cache';
console.log('Running getTodo with id: ', id);
const data = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
const post: TodoT = await data.json();
return post;
}
// components\todo\Todo.tsx
type Props = {
id: number;
};
export async function Todo({ id }: Props) {
const todo = await getTodo(id);
return (
<div className='flex gap-2'>
<div className='font-bold'>{todo.id}.</div>
<div className='italic'>{todo.title}</div>
</div>
);
}
And load them in a new route (/chapter-15/todo-data-cache):
// app\chapter-15\todo-data-cache\page.tsx
export default async function TodoDataCachePage() {
return (
<>
<h1>Todo</h1>
<Todo id={3} />
<Todo id={3} />
</>
);
}
Executing getTodo(3) triggers caching. When running next build, the console output appears only once:
Running getTodo with id: 3
We now also have a data cache entry for the todo with id 3:
/* prettier-ignore */
// .next\cache\fetch-cache\26aaf1ca65bbe27aad40fed07db13152ec7944d208771bb4a4c1c4902f45f8ce
{"kind":"FETCH","data":{"headers":{"access-control-allow-credentials":"true","alt-svc":"h3=\":443\"; ma=86400","cache-control":"max-age=43200","cf-cache-status":"REVALIDATED","cf-ray":"a21c20f59ec11b57-BRU","connection":"keep-alive","content-encoding":"br","content-type":"application/json; charset=utf-8","date":"Mon, 27 Jul 2026 13:53:10 GMT","etag":"W/\"54-J3JtLgWuXjgj1OZdyAcKAqOaKHo\"","expires":"-1","nel":"{\"report_to\":\"heroku-nel\",\"response_headers\":[\"Via\"],\"max_age\":3600,\"success_fraction\":0.01,\"failure_fraction\":0.1}","pragma":"no-cache","report-to":"{\"group\":\"heroku-nel\",\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?s=vnwcBXW2mQz4dTGAObLHn3nsxh2rgpiHr96fwwPTWaM%3D\\u0026sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d\\u0026ts=1785091296\"}],\"max_age\":3600}","reporting-endpoints":"heroku-nel=\"https://nel.heroku.com/reports?s=vnwcBXW2mQz4dTGAObLHn3nsxh2rgpiHr96fwwPTWaM%3D&sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d&ts=1785091296\"","server":"cloudflare","transfer-encoding":"chunked","vary":"Origin, Accept-Encoding","via":"2.0 heroku-router","x-content-type-options":"nosniff","x-powered-by":"Express","x-ratelimit-limit":"1000","x-ratelimit-remaining":"999","x-ratelimit-reset":"1785091339"},"body":"ewogICJ1c2VySWQiOiAxLAogICJpZCI6IDMsCiAgInRpdGxlIjogImZ1Z2lhdCB2ZW5pYW0gbWludXMiLAogICJjb21wbGV0ZWQiOiBmYWxzZQp9","status":200,"url":"https://jsonplaceholder.typicode.com/todos/3"},"revalidate":900,"tags":[]}
Notice the url property (last line) pointing to https://jsonplaceholder.typicode.com/todos/3 and "revalidate":900 which is the 15 minutes default value for "use cache". So, data cache and function cache work in tandem here, giving us seamless caching.
Look at this next example:
// app\chapter-15\todo-different\page.tsx
async function differentGetTodo(id: number) {
'use cache';
const data = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
const todo = await data.json();
console.log(todo);
}
export default async function DifferentTodoPage() {
await differentGetTodo(3);
return <div>hello</div>;
}
We created a new route (chapter-15/todo-different). Instead of reusing our getTodo function, we created a new one, also cached. The goal of this is to have a different cached function that calls the same jsonplaceholder endpoint with the fetch API. We now have two functions calling the same endpoint with the same id.
run build
When we inspect the data cache folder, we only find a single entry for todo 3.
In our previous example (chapter-15/todo-data-cache) we used getTodo(3). The return value of getTodo(3) was cached. Behind the scenes, Next also cached the response from the fetch API for https://jsonplaceholder.typicode.com/todos/3 in the data cache. I showed the cache file above.
We added and build route chapter-15/todo-different. This uses differentGetTodo(3). This is different from getTodo and will create it's own cache entry (function cache). However, underneath it calls the fetch API with the exact same url as the getTodo(3) function. When executing the internal fetch call, Next reused the existing Data Cache file generated by getTodo(3) rather than creating a duplicate on disk.
We 'proved' this because our data cache folder only has one entry for todo 3. In other words, this example demonstrates that the function cache uses the data cache.
This distinction is of limited practical use. But it's interesting and fits perfectly inside the subject of this series (caching) so I thought I would share this.
If you want to support my writing, you can donate with paypal.
Top comments (0)