缓存
缓存是把「计算结果」存下来,下次直接复用。Next.js 在 App Router 上引入了 Cache Components 模型,用 use cache 指令显式声明可以缓存的部分,其它需要运行时数据的部分则用 <Suspense> 包起来。整体思路叫 Partial Prerendering (PPR)。
启用 Cache Components
在 next.config.ts 开启:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;
use cache 指令
use cache 缓存异步函数或组件的返回值。可以放在两个粒度:
数据级缓存
import { cacheLife } from 'next/cache';
export async function getUsers() {
'use cache';
cacheLife('hours');
return db.query('SELECT * FROM users');
}
函数的 参数 和闭包变量会自动成为缓存 key 的一部分,因此不同入参会产生不同的缓存条目。
UI 级缓存
整个组件/页面都可以缓存:
import { cacheLife } from 'next/cache';
export default async function Page() {
'use cache';
cacheLife('hours');
const users = await db.query('SELECT * FROM users');
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
如果在文件顶部写
'use cache',该文件里所有导出都会被缓存。
流式未缓存的数据
对于每次请求都要最新的数据(比如调用 API 或数据库),不要 加 'use cache',用 <Suspense> 包起来即可:
import { Suspense } from 'react';
async function LatestPosts() {
const data = await fetch('https://api.example.com/posts');
const posts = await data.json();
return <ul>{posts.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>;
}
export default function Page() {
return (
<>
<h1>我的博客</h1>
<Suspense fallback={<p>加载最新文章…</p>}>
<LatestPosts />
</Suspense>
</>
);
}
fallback 会进入静态 HTML 外壳,真实内容在请求时流式注入。
运行时 API
cookies、headers、searchParams、params(除非提供了 generateStaticParams)只能在请求时拿到。访问它们的组件 必须包在 <Suspense> 里:
import { cookies } from 'next/headers';
import { Suspense } from 'react';
async function UserGreeting() {
const theme = (await cookies()).get('theme')?.value || 'light';
return <p>你的主题:{theme}</p>;
}
export default function Page() {
return (
<Suspense fallback={<p>加载中…</p>}>
<UserGreeting />
</Suspense>
);
}
把运行时值喂给缓存函数
从运行时拿到值后,可以当参数传给 use cache 函数:
import { cookies } from 'next/headers';
import { Suspense } from 'react';
async function CachedContent({ sessionId }: { sessionId: string }) {
'use cache';
return <div>{await fetchUserData(sessionId)}</div>;
}
async function ProfileContent() {
const session = (await cookies()).get('session')?.value;
return <CachedContent sessionId={session!} />;
}
export default function Page() {
return (
<Suspense fallback={<div>加载中…</div>}>
<ProfileContent />
</Suspense>
);
}
非确定性操作
Math.random()、Date.now()、crypto.randomUUID() 每次结果不同。想每次请求都给不同值?调 connection() 让渲染下沉到请求时:
import { connection } from 'next/server';
import { Suspense } from 'react';
async function Unique() {
await connection();
return <p>请求 ID: {crypto.randomUUID()}</p>;
}
export default function Page() {
return (
<Suspense fallback={<p>加载中…</p>}>
<Unique />
</Suspense>
);
}
Partial Prerendering 概览
Cache Components 开启后,每条路由在构建期会生成一个 静态 shell:
'use cache'的结果进入 shell。<Suspense>里未缓存的部分在请求时流式填入。- 纯计算(同步 I/O、模块导入)被自动放入 shell。
这就是默认的 PPR 渲染行为,同一条路由能同时做到「首字节超快 + 个性化最新」。