本节摘要:数据是页面的灵魂,但"数据在何时、何地获取"决定了页面的性能与 SEO。本节讲清四种数据获取策略(CSR/SSR/SSG/ISR)的原理、实现与选择逻辑,重点掌握服务器组件里的 async 取数与 Next.js 的缓存模型。
阅读完本节,你应当能够:
同一个页面,数据获取方式不同,体验天差地别:
直觉类比:数据获取像"餐厅备菜"——SSG 是"提前把菜全部做好放展示柜"(顾客来了直接端,但菜是提前的);SSR 是"顾客点单现炒"(最新鲜但慢);ISR 是"主菜提前做,每隔几小时重做一轮"(兼顾快与新)。
💡 关键直觉:Next.js 的默认姿势是"能静态就静态"。服务器组件默认在构建时渲染成静态页面;只有你主动标记(动态 API、cookies、请求头等)才变成动态。这个"静态优先"的默认值,是 Next.js 性能好的根本原因。
App Router 中,服务器组件可以直接 async + await 取数,不用 useEffect、不用 fetch 包装:
// app/posts/page.tsx —— 默认静态渲染(SSG) export default async function PostsPage() { const res = await fetch("https://api.example.com/posts", { cache: "force-cache", // 默认:构建时取一次,静态缓存 }); const posts = await res.json(); return ( <ul> {posts.map((p) => <li key={p.id}>{p.title}</li>)} </ul> ); }
| 选项 | 行为 | 渲染方式 |
|---|---|---|
cache: "force-cache"(默认) |
构建时取一次,静态缓存 | 静态(SSG) |
cache: "no-store" |
每次请求都重新取 | 动态(SSR) |
next: { revalidate: 60 } |
缓存 60 秒后后台刷新 | 静态 + ISR |
// ISR:每 60 秒重新验证一次 export default async function Page() { const res = await fetch("https://api.example.com/posts", { next: { revalidate: 60 }, // 60 秒增量再生 }); const posts = await res.json(); return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>; }
非 fetch 取数(如直接查数据库)用路由段配置控制:
// app/dashboard/page.tsx export const dynamic = "force-dynamic"; // 强制动态渲染(SSR) // 或 revalidate export const revalidate = 60; // ISR:60 秒
交互性强的组件(需要 loading、轮询、用户触发刷新)用客户端取数:
"use client"; import { useEffect, useState } from "react"; export function LiveData() { const [data, setData] = useState(null); useEffect(() => { fetch("/api/stats").then((r) => r.json()).then(setData); }, []); return <div>{data ? data.value : "加载中..."}</div>; }

实战经验:
| 误区 | 现象 | 正解 |
|---|---|---|
| 所有页面都实时取数 | 服务器压力大、页面慢 | 按策略选择,默认静态 |
| 以为服务器组件不能取数 | 还在用 useEffect 取数 | async 直接 await |
| fetch 缓存不生效 | 页面还是动态 | 检查 cache 选项与动态 API 使用 |
| 改数据页面不更新 | 静态缓存 | ISR revalidate 或强制动态 |
| 忘记 dynamic 配置 | 数据库数据不刷新 | 用 revalidate 或 force-dynamic |
// 1. SSG:app/blog/page.tsx(默认静态) export default async function Blog() { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const posts = await res.json(); return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>; } // 2. ISR:app/news/page.tsx(60 秒刷新) export const revalidate = 60; // 3. SSR:app/profile/page.tsx(每次请求实时) export const dynamic = "force-dynamic";
运行 npm run build,观察三个页面的构建标记:博客是 ○(静态)、新闻是 ○(静态+ISR)、个人页是 ƒ(动态)——用构建输出验证你的策略选择是否正确。
缓存失效:ISR 的 revalidate 是"时间驱动"的(到时间后台刷新)。需要"事件驱动"失效(数据变了立即刷新)时,配合 Server Actions 的 revalidatePath 或 revalidateTag:
// 数据变更后立即刷新相关页面 "use server"; export async function updatePost(id: string) { await prisma.post.update({ where: { id }, data: { ... } }); revalidatePath("/posts"); // 立即让 /posts 重新生成 revalidatePath(`/posts/${id}`); }
并行取数:页面有多个独立数据时,用 Promise.all 并行(比串行快):
export default async function DashboardPage() { // 串行:慢(先等 A 再等 B) // const a = await getA(); const b = await getB(); // 并行:快(A、B 同时进行) const [a, b] = await Promise.all([getA(), getB()]); return <Dashboard a={a} b={b} />; }
一句话总结:数据获取没有"最好",只有"最适合"——按 SEO 需求与变化频率选择,是数据层设计的第一原则。
问:服务器组件里能同时用 fetch 和查数据库吗?
能。fetch 外部 API 与直接查数据库(Prisma)都支持,按需选择。注意:fetch 的缓存选项只对 fetch 生效,数据库查询用 dynamic/revalidate 路由段配置控制。
问:revalidate 后数据什么时候更新?
revalidate 是"后台刷新":缓存过期后,下一次请求触发后台重新生成,用户拿到的是旧缓存,生成完成后新请求拿新数据。它不是"到期立即删缓存",而是"到期后下一次请求刷新"。
问:如何强制刷新某个页面?
开发中想立即看到最新数据:Server Action 里调 revalidatePath;或临时把 dynamic 设为 force-dynamic;生产环境等待 revalidate 自然过期。
问:客户端组件里还能用 fetch 吗?
能,但注意:客户端 fetch 不走 Next.js 的缓存模型(服务端 fetch 才有 cache 选项),且会造成"先加载后取数"的体验。优先服务器组件取数。
数据获取的核心心法是"能静态就静态,能缓存就缓存"——SSG 构建时生成、ISR 定时刷新、SSR 请求时渲染、CSR 浏览器取数,按 SEO 需求与数据变化频率选择。记住构建输出里的 ○ 和 ƒ 标记,它们是策略选择的直观验证。