Next.js 16 introduces an opt-in caching model. By default nothing is cached — you cache deliberately, with "use cache" + cacheTag + cacheLife.
| Old | New (16) | |
|---|---|---|
| Default | fetch was cached forever | fetch is uncached |
| Opt-in cache | fetch(url, { next: { revalidate } }) | "use cache" directive |
| Invalidation | revalidatePath / revalidateTag | revalidateTag(tag) (still works) |
| Purpose | Full route caching | Cache any function or component |
Why the change? The old model conflated "is this request cached?" with "is this route static?" — leading to surprising production behavior. The new one is explicit.
Add the directive at the top of a function or file:
1// lib/data/courses.ts
2"use cache";
3import { db } from "@/lib/db";
4
5export async function getPublishedCourses() {
6 return db.course.findMany({ where: { status: "PUBLISHED" } });
7}Calling getPublishedCourses() from anywhere — a Server Component, an API route, a Server Action — returns the cached value when the cache is warm and fresh.
1"use cache";
2import { cacheTag } from "next/cache";
3
4export async function getCourse(slug: string) {
5 cacheTag("course", `course:${slug}`);
6 return db.course.findUnique({ where: { slug } });
7}Now you can revalidate every cached entry for that course with one call:
1import { revalidateTag } from "next/cache";
2revalidateTag(`course:${slug}`);Or wipe all cached courses:
1revalidateTag("course");1"use cache";
2import { cacheLife } from "next/cache";
3
4export async function getHomeFeed() {
5 cacheLife("minutes"); // alias for { stale: 60s, revalidate: 5min, expire: 30min }
6 return /* … */;
7}Named profiles:
| Profile | Stale | Revalidate | Expire |
|---|---|---|---|
"seconds" | 0 | 1s | 60s |
"minutes" | 60s | 5m | 30m |
"hours" | 5m | 1h | 1d |
"days" | 5m | 12h | 7d |
"weeks" | 1h | 1d | 30d |
"max" | 5m | 1y | 1y |
Custom values:
1cacheLife({ stale: 30, revalidate: 300, expire: 3600 });The terminology matters:
This is the same model CDNs use for Cache-Control: stale-while-revalidate.
1// app/(marketing)/page.tsx
2"use cache";
3import { cacheLife } from "next/cache";
4
5export default async function Home() {
6 cacheLife("hours");
7 const posts = await db.blog.findMany({ take: 6 });
8 return <Hero posts={posts} />;
9}1// components/Reviews.tsx
2"use cache";
3import { cacheTag, cacheLife } from "next/cache";
4
5export async function Reviews({ courseId }: { courseId: string }) {
6 cacheTag(`reviews:${courseId}`);
7 cacheLife("hours");
8 const reviews = await db.review.findMany({ where: { courseId } });
9 return /* … */;
10}The page can be uncached while only the Reviews block is cached — a form of partial caching that the old model never made easy.