Teach cache lifecycle APIs in Next.js 16 - cacheLife(), cacheTag(), updateTag(), refresh(), revalidateTag(). Use when managing cache invalidation, setting cache policies, or implementing cache tags.
Teaches Next.js 16 cache lifecycle APIs (cacheLife, cacheTag, revalidateTag) for managing cache policies and invalidation. Use when implementing fine-grained cache control, setting cache durations, or handling targeted cache invalidation strategies.
/plugin marketplace add djankies/claude-configs/plugin install nextjs-16@claude-configsThis skill is limited to using the following tools:
Next.js 16 introduces comprehensive cache lifecycle APIs for fine-grained control over caching behavior. These APIs work together to manage cache policies, tagging, and invalidation across both private (CDN) and remote (server) caches.
Sets cache duration profiles for fetch requests and route handlers. Replaces the need for manual revalidation intervals.
import { unstable_cacheLife as cacheLife } from 'next/cache';
export async function GET() {
'use cache';
cacheLife('hours');
const data = await fetch('https://api.example.com/data');
return Response.json(data);
}
Built-in Profiles:
default: 15 minutes stale, 1 day revalidate, 1 week expireseconds: 1 second stale, 10 seconds revalidate, 1 minute expireminutes: 5 minutes stale, 1 hour revalidate, 1 day expirehours: 5 minutes stale, 1 hour revalidate, 1 day expiredays: 5 minutes stale, 12 hours revalidate, 7 days expireweeks: 5 minutes stale, 24 hours revalidate, 30 days expiremax: 5 minutes stale, indefinite revalidate, indefinite expireCustom Profiles:
cacheLife({
stale: 60,
revalidate: 3600,
expire: 86400,
});
Multiple Profiles:
cacheLife('hours', 'days');
Associates cache entries with tags for targeted invalidation. Tags enable logical grouping of cached content.
import { unstable_cacheTag as cacheTag } from 'next/cache';
export async function GET() {
'use cache';
cacheTag('products');
const products = await db.query.products.findMany();
return Response.json(products);
}
Multiple Tags:
cacheTag('products', 'featured');
Tag Naming Convention:
user:123, posts:featuredInvalidates all cache entries associated with a tag. revalidateTag() has a new signature in Next.js 16.
Breaking Change in Next.js 16:
import { revalidateTag } from 'next/cache';
export async function POST(request: Request) {
const body = await request.json();
await revalidateTag('products');
return Response.json({ revalidated: true });
}
The new signature is async and returns a Promise. Previous versions were synchronous.
Server Action Usage:
'use server';
import { revalidateTag } from 'next/cache';
export async function updateProducts() {
await db.products.update();
await revalidateTag('products');
await revalidateTag('featured');
}
Forces immediate revalidation of the current page without invalidating tags.
import { refresh } from 'next/cache';
export async function POST() {
'use server';
await db.update();
refresh();
return { success: true };
}
Caches at the edge closest to users. Controlled by Cache-Control headers.
export async function GET() {
'use cache';
cacheLife('hours');
return Response.json(data, {
headers: {
'Cache-Control': 'private, max-age=3600',
},
});
}
Use Cases:
Caches on the Next.js server or shared infrastructure. Controlled by cacheLife() and tags.
export async function GET() {
'use cache';
cacheLife('days');
cacheTag('public-data');
return Response.json(data);
}
Use Cases:
Combine both for optimal performance:
export async function GET(request: Request) {
'use cache';
cacheLife('hours');
cacheTag('products');
const userId = request.headers.get('x-user-id');
const products = await getProducts(userId);
return Response.json(products, {
headers: {
'Cache-Control': 'private, s-maxage=3600, stale-while-revalidate=86400',
},
});
}
Invalidate immediately when data changes:
'use server';
import { revalidateTag } from 'next/cache';
export async function createProduct(formData: FormData) {
await db.products.create({
name: formData.get('name'),
});
await revalidateTag('products');
await revalidateTag('homepage');
}
Use cacheLife() with appropriate profiles for periodic updates:
export async function GET() {
'use cache';
cacheLife('hours');
const stats = await getAnalytics();
return Response.json(stats);
}
Tag different aspects of data for targeted updates:
export async function getProduct(id: string) {
'use cache';
cacheTag(`product:${id}`, 'products', 'catalog');
return db.query.products.findFirst({ where: { id } });
}
export async function updateProduct(id: string, data: any) {
'use server';
await db.products.update({ where: { id }, data });
await revalidateTag(`product:${id}`);
}
Invalidate related caches together:
export async function deleteProduct(id: string) {
'use server';
const product = await db.products.findUnique({ where: { id } });
await db.products.delete({ where: { id } });
await Promise.all([
revalidateTag(`product:${id}`),
revalidateTag('products'),
revalidateTag(`category:${product.categoryId}`),
revalidateTag('search-results'),
]);
}
import {
unstable_cacheLife as cacheLife,
unstable_cacheTag as cacheTag,
revalidateTag,
} from 'next/cache';
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
'use cache';
cacheLife('hours');
cacheTag(`product:${params.id}`, 'products');
const product = await db.query.products.findFirst({
where: { id: params.id },
with: { reviews: true, category: true },
});
return Response.json(product, {
headers: {
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
},
});
}
export async function PUT(
request: Request,
{ params }: { params: { id: string } }
) {
const body = await request.json();
const product = await db.products.update({
where: { id: params.id },
data: body,
});
await Promise.all([
revalidateTag(`product:${params.id}`),
revalidateTag('products'),
revalidateTag(`category:${product.categoryId}`),
]);
return Response.json({ success: true, product });
}
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const product = await db.products.findUnique({
where: { id: params.id },
});
await db.products.delete({ where: { id: params.id } });
await Promise.all([
revalidateTag(`product:${params.id}`),
revalidateTag('products'),
revalidateTag(`category:${product.categoryId}`),
revalidateTag('search-results'),
revalidateTag('homepage'),
]);
return Response.json({ success: true });
}
When caching database queries, optimize query selection and pagination to prevent performance issues:
export async function GET() {
'use cache';
cacheLife('days');
cacheTag('blog-posts');
const posts = await db.query.posts.findMany();
return Response.json(posts);
}
export async function GET(request: Request) {
'use cache';
const userId = request.headers.get('x-user-id');
cacheTag(`user:${userId}`);
cacheLife('minutes');
const data = await getUserData(userId);
return Response.json(data, {
headers: { 'Cache-Control': 'private, max-age=300' },
});
}
export async function webhookHandler(request: Request) {
const event = await request.json();
if (event.type === 'product.updated') {
await revalidateTag(`product:${event.productId}`);
await revalidateTag('products');
}
return Response.json({ received: true });
}
When upgrading from Next.js 15 to 16:
Update all revalidateTag() calls to handle Promises:
revalidateTag('tag');
becomes:
await revalidateTag('tag');
Replace manual revalidation with cacheLife():
export const revalidate = 3600;
becomes:
cacheLife('hours');
Use cacheTag() instead of custom headers for invalidation
Import APIs from 'next/cache' with unstable_ prefix where required
Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems. Use when implementing auth systems, securing APIs, or debugging security issues.