Next.js 渲染与数据获取
这一块是 Next.js 最核心的基础之一。很多看起来像“API 记忆题”的东西,背后其实都在回答同一个问题:
- 这段内容什么时候生成
- 在服务端还是客户端生成
- 数据什么时候请求
- 请求结果缓存多久
先把几种渲染方式分开
1. CSR
客户端渲染。HTML 先到浏览器,再由 JS 拉数据并渲染。
适合:
- 强交互页面
- SEO 不敏感页面
- 依赖浏览器状态较重的页面
2. SSR
服务端渲染。每次请求到来时,在服务端生成 HTML。
适合:
- 首屏内容依赖请求上下文
- SEO 重要
- 页面内容需要实时性
3. SSG
静态生成。构建时就生成 HTML。
适合:
- 内容更新频率低
- 页面结构稳定
- 营销页、文档页、博客页
4. ISR
增量静态再生成。页面先静态输出,再按设 定时间重建。
适合:
- 不需要每次请求都实时生成
- 但又不能永远是构建时内容
Pages Router 的数据获取方式
getStaticProps
export async function getStaticProps() {
const posts = await fetch('https://api.example.com/posts').then((res) =>
res.json()
);
return {
props: { posts },
revalidate: 60,
};
}
getServerSideProps
export async function getServerSideProps() {
const posts = await fetch('https://api.example.com/posts').then((res) =>
res.json()
);
return {
props: { posts },
};
}
这套方式很清楚,但把“页面”和“取数生命周期”分成了两个地方去写。
App Router 的数据获取方式
App Router 里,服务端取数通常直接写进组件。
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 },
});
if (!res.ok) {
throw new Error('failed to fetch posts');
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post: { id: number; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
这套写法的核心变化是:页面本身就是服务端执行单元。
Server Component 和 Client Component 怎么配合
默认是 Server Component
export default async function Page() {
const data = await fetch('https://api.example.com/profile').then((res) =>
res.json()
);
return <div>{data.name}</div>;
}
需要交互时再下放到 Client Component
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
一个很实用的经验是:
- 先尽量让页面留在服务端
- 只把真正需要交互、浏览器 API、状态管理的部分切到客户端
fetch 在 Next 里不只是浏览器那个 fetch
在 App Router 里,fetch 会接入 Next 的缓存和重验证模型。也就是说,写法看起来一样,但语义比普通 React 项目里的 fetch 更重。
await fetch('https://api.example.com/products', {
cache: 'force-cache',
});
await fetch('https://api.example.com/products', {
cache: 'no-store',
});
await fetch('https://api.example.com/products', {
next: { revalidate: 300 },
});
流式渲染和 Suspense
App Router 对流式渲染支持更自然。
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading chart...</div>}>
<ChartSection />
</Suspense>
</div>
);
}
这意味着页面不必等所有慢数据都准备好之后再一次性返回。
客户端数据层什么时候还需要
即使在 App Router 里,React Query、SWR 这类客户端数据层仍然很有用。典型场景有:
- 频繁轮询
- 用户操作后的局部刷新
- 客户端缓存 复用
- 多组件共享同一份客户端请求状态
'use client';
import { useQuery } from '@tanstack/react-query';
export default function Notifications() {
const { data, isLoading } = useQuery({
queryKey: ['notifications'],
queryFn: () => fetch('/api/notifications').then((res) => res.json()),
});
if (isLoading) return <div>Loading...</div>;
return <div>{data.length}</div>;
}
一个简单判断方式
优先服务端取数
适合:
- 首屏需要内容
- SEO 重要
- 页面结构依赖后端数据
- 不希望把大量请求逻辑下发到浏览器
优先客户端取数
适合:
- 页面已经渲染出来后再做局部刷新
- 交互频繁
- 实时性强
- 状态共享主要发生在客户端
常见误区
1. 以为 App Router 就完全不需要客户端取数
并不是。只是“服务端优先”变成了新的默认思路。
2. 以为 SSR、SSG、ISR 是三选一
现实项目里常常是混合的。不同页面、不同数据块可以走不同策略。
3. 低估 fetch 缓存语义
在 Next 里,fetch 已经不是单纯网络请求函数,它也是缓存配置入口。