Next.js Caching and Revalidation
Cache data in Next.js with use cache, set lifetimes, and refresh it on demand with tags.
TL;DR
- Enable
cacheComponents, then add'use cache'to cache an async function. - Set the lifetime with
cacheLife()and label data withcacheTag(). - Refresh data after a change with
updateTag()orrevalidatePath().
Cache Components
cacheComponentsEnable the caching model in your config; data stays dynamic until you cache it.
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;'use cache'Cache an async function so calls with the same arguments reuse the result.
export async function getProduct(id: string) {
'use cache';
const rows = await sql`
SELECT * FROM products WHERE id = ${id}
`;
return rows[0];
}Cached ComponentCache a whole component, including the data it fetches and the markup it returns.
export async function Footer() {
'use cache';
const links = await getFooterLinks();
return <footer>{links.length} links</footer>;
}Lifetimes and Tags
cacheLife()Pick a preset lifetime such as 'minutes', 'hours', 'days', or 'max'.
import { cacheLife } from 'next/cache';
export async function getPosts() {
'use cache';
cacheLife('hours');
return sql`SELECT * FROM posts`;
}Custom LifetimePass an object to set the stale, revalidate, and expire times in seconds.
cacheLife({
stale: 300, // client reuse: 5 min
revalidate: 900, // refresh after 15 min
expire: 86400, // drop after 1 day
});cacheTag()Label cached data so you can refresh it by name later.
import { cacheTag } from 'next/cache';
export async function getPosts() {
'use cache';
cacheTag('posts');
return sql`SELECT * FROM posts`;
}Refreshing on Demand
updateTag()Expire tagged data immediately inside a Server Action, so the next render is fresh.
'use server';
import { updateTag } from 'next/cache';
export async function addPost(form: FormData) {
await savePost(form);
updateTag('posts');
}revalidateTag()Mark tagged data stale and refresh it in the background on the next visit.
import { revalidateTag } from 'next/cache';
export async function POST() {
revalidateTag('posts', 'max');
return Response.json({ ok: true });
}revalidatePath()Refresh everything cached for one page or route by its URL.
import { revalidatePath } from 'next/cache';
revalidatePath('/blog');
revalidatePath('/blog/[slug]', 'page');Without Cache Components
force-cacheCache a single request in apps that have not enabled Cache Components.
const res = await fetch(url, {
cache: 'force-cache',
next: { tags: ['posts'] },
});next.revalidateRefresh a cached request in the background after a number of seconds.
const res = await fetch(url, {
next: { revalidate: 3600 },
});export const revalidateRebuild a static page in the background on a timer, known as ISR.
// app/blog/page.tsx
export const revalidate = 60; // secondsTips
- Call
updateTag()in a Server Action when the user must see their own change immediately on the next render. - Pick a
cacheLife()profile such as'hours'or'days'that matches how often the underlying data really changes.
Warnings
- Calling
revalidateTag()with one argument is deprecated in Next.js 16, so pass a profile such as'max'as the second. - You cannot read
cookies()orheaders()inside a'use cache'function, so read them outside and pass the values in.
In Practice
Cache a list of posts for hours, then refresh it the moment someone adds a post.
- use cache stores the query result, so most visits never hit the database.
- cacheLife sets how long that result stays fresh without any action.
- cacheTag gives the cached data a name that other code can target.
- The Server Action calls updateTag, so the author sees the new post at once.
// lib/posts.ts
import { cacheLife, cacheTag } from 'next/cache';
import { sql } from '@/lib/db';
export async function getPosts() {
'use cache';
cacheLife('hours');
cacheTag('posts');
return sql`SELECT * FROM posts ORDER BY id DESC`;
}
// app/posts/actions.ts
'use server';
import { updateTag } from 'next/cache';
import { sql } from '@/lib/db';
export async function addPost(form: FormData) {
const title = String(form.get('title') ?? '');
await sql`INSERT INTO posts (title) VALUES (${title})`;
updateTag('posts');
}FAQ
Data is dynamic by default, and you opt in to caching. With cacheComponents: true, add 'use cache' to a function or component to cache it. Without that option, cache fetch calls with cache: 'force-cache'.
updateTag() expires the data at once, so the next render is fresh. It only works in Server Actions. revalidateTag() marks data as stale and refreshes it in the background, and it also works in Route Handlers.
revalidatePath() refreshes one page or layout by its URL. revalidateTag() refreshes every cached item with a given tag, across all the pages that use it.
Yes, in apps that do not enable Cache Components. With cacheComponents: true, the revalidate route segment config is removed, and you use cacheLife() instead.