Loading...
Loading...
Next.js 16 Cache Components - PPR, use cache directive, cacheLife, cacheTag, updateTag
npx skill4agent add samunderwood/agent-skills next-cache-components// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;experimental.pprexport default function Page() {
return (
<header>
<h1>Our Blog</h1> {/* Static - instant */}
<nav>...</nav>
</header>
);
}use cacheasync function BlogPosts() {
"use cache";
cacheLife("hours");
const posts = await db.posts.findMany();
return <PostList posts={posts} />;
}import { Suspense } from "react";
export default function Page() {
return (
<>
<BlogPosts /> {/* Cached */}
<Suspense fallback={<p>Loading...</p>}>
<UserPreferences /> {/* Dynamic - streams in */}
</Suspense>
</>
);
}
async function UserPreferences() {
const theme = (await cookies()).get("theme")?.value;
return <p>Theme: {theme}</p>;
}use cache"use cache";
export default async function Page() {
// Entire page is cached
const data = await fetchData();
return <div>{data}</div>;
}export async function CachedComponent() {
"use cache";
const data = await fetchData();
return <div>{data}</div>;
}export async function getData() {
"use cache";
return db.query("SELECT * FROM posts");
}"use cache"; // Default: 5m stale, 15m revalidate"use cache: remote"; // Platform-provided cache (Redis, KV)"use cache: private"; // For compliance, allows runtime APIscacheLife()import { cacheLife } from "next/cache";
async function getData() {
"use cache";
cacheLife("hours"); // Built-in profile
return fetch("/api/data");
}'default''minutes''hours''days''weeks''max'async function getData() {
"use cache";
cacheLife({
stale: 3600, // 1 hour - serve stale while revalidating
revalidate: 7200, // 2 hours - background revalidation interval
expire: 86400, // 1 day - hard expiration
});
return fetch("/api/data");
}cacheTag()import { cacheTag } from "next/cache";
async function getProducts() {
"use cache";
cacheTag("products");
return db.products.findMany();
}
async function getProduct(id: string) {
"use cache";
cacheTag("products", `product-${id}`);
return db.products.findUnique({ where: { id } });
}updateTag()"use server";
import { updateTag } from "next/cache";
export async function updateProduct(id: string, data: FormData) {
await db.products.update({ where: { id }, data });
updateTag(`product-${id}`); // Immediate - same request sees fresh data
}revalidateTag()"use server";
import { revalidateTag } from "next/cache";
export async function createPost(data: FormData) {
await db.posts.create({ data });
revalidateTag("posts"); // Background - next request sees fresh data
}cookies()headers()searchParamsuse cache// Wrong - runtime API inside use cache
async function CachedProfile() {
"use cache";
const session = (await cookies()).get("session")?.value; // Error!
return <div>{session}</div>;
}
// Correct - extract outside, pass as argument
async function ProfilePage() {
const session = (await cookies()).get("session")?.value;
return <CachedProfile sessionId={session} />;
}
async function CachedProfile({ sessionId }: { sessionId: string }) {
"use cache";
// sessionId becomes part of cache key automatically
const data = await fetchUserData(sessionId);
return <div>{data.name}</div>;
}use cache: privateasync function getData() {
"use cache: private";
const session = (await cookies()).get("session")?.value; // Allowed
return fetchData(session);
}async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
"use cache";
// Cache key = userId (closure) + filter (argument)
return fetch(`/api/users/${userId}?filter=${filter}`);
};
return getData("active");
}import { Suspense } from "react";
import { cookies } from "next/headers";
import { cacheLife, cacheTag } from "next/cache";
export default function DashboardPage() {
return (
<>
{/* Static shell - instant from CDN */}
<header>
<h1>Dashboard</h1>
</header>
<nav>...</nav>
{/* Cached - fast, revalidates hourly */}
<Stats />
{/* Dynamic - streams in with fresh data */}
<Suspense fallback={<NotificationsSkeleton />}>
<Notifications />
</Suspense>
</>
);
}
async function Stats() {
"use cache";
cacheLife("hours");
cacheTag("dashboard-stats");
const stats = await db.stats.aggregate();
return <StatsDisplay stats={stats} />;
}
async function Notifications() {
const userId = (await cookies()).get("userId")?.value;
const notifications = await db.notifications.findMany({
where: { userId, read: false },
});
return <NotificationList items={notifications} />;
}| Old Config | Replacement |
|---|---|
| |
| Remove (default behavior) |
| |
| |
| |
Math.random()Date.now()use cacheimport { connection } from "next/server";
async function DynamicContent() {
await connection(); // Defer to request time
const id = crypto.randomUUID(); // Different per request
return <div>{id}</div>;
}