服务器组件的渲染
React的传统组件,即客户端组件,当其自身的**状态(state)**或父组件的状态发生变化时会重新渲染。然而,在服务器组件中不能使用钩子(hooks)。这意味着服务器组件没有状态。如下例所示,即使父组件的状态发生变化,服务器组件也不会重新渲染。那么,服务器组件何时会重新渲染呢?
服务器组件渲染策略
根据Next.js官方文档,渲染过程采用三种策略:Static、Dynamic和Streaming。
Static Rendering
Static Rendering是Next.js的默认渲染策略。这种策略的核心特点是在构建时渲染一次组件,然后重复使用结果。
- 构建时渲染
- 服务器组件在应用程序构建时渲染
- 渲染结果缓存在CDN中并提供给所有用户
- 数据不变性
- 一旦渲染的组件数据基本上不会改变
- 即使刷新页面,也会显示相同的数据
- 客户端状态变化不会影响服务器组件数据
- 重新验证机制
- 可以通过
revalidateTag()
或revalidatePath()
手动重新渲染
- 可以通过
-
刷新屏幕时
可以看到,即使更改状态并重新加载,数据也不会改变。
-
重新验证时
可以看到,当执行Next.js的
revalidateTag
时,它会使服务器组件的缓存失效,重新获取数据并重新渲染。
Dynamic Rendering
Dynamic Rendering是Next.js的渲染策略,它为每个用户请求在服务器上重新渲染组件。
如果服务器组件满足以下两个条件之一,它会自动切换到Dynamic Rendering:
- 使用Dynamic API
- 在fetch选项中发现
{ cache: 'no-store' }
渲染策略决策表
使用Dynamic API | 数据缓存 | 渲染结果 |
---|---|---|
❌ 否 | ✅ 已缓存 | Static |
✅ 是 | ✅ 已缓存 | Dynamic |
❌ 否 | ❌ 未缓存 | Dynamic |
✅ 是 | ❌ 未缓存 | Dynamic |
换句话说,只有在不使用Dynamic API并对fetch应用缓存时,才会进行Static Rendering。
Dynamic APIs
Dynamic API指的是依赖于请求时上下文的API。让我们看看主要的Dynamic API。
1. cookies()
import { cookies } from 'next/headers';
async function Component() {
const cookieStore = cookies();
const theme = cookieStore.get('theme');
return <div>Current theme: {theme?.value}</div>;
}
2. headers()
import { headers } from 'next/headers';
async function Component() {
const headersList = headers();
const userAgent = headersList.get('user-agent');
return <div>Accessing from: {userAgent}</div>;
}
3. searchParams
// app/page.tsx
export default function Page({
searchParams,
}: {
searchParams: { query: string };
}) {
return <div>Search query: {searchParams.query}</div>;
}
与Parallel Routes一起使用时的注意事项
如下例所示,使用searchParams控制模态框时,可能会因Dynamic Rendering而出现问题:
- searchParams变化时服务器组件会重新渲染
- 数据获取导致模态框延迟
- 模态框打开的同时数据变化
// app/@modal/default.tsx
'use client'
import {useSearchParams} from "next/navigation";
import {Modal} from "@/app/@modal/(.)post/[id]/modal";
function Default() {
const searchParam = useSearchParams();
const isModalOpen = searchParam.get('modal') === 'true';
if (!isModalOpen) {
return null;
}
return (
<Modal>modal!!</Modal>
);
}
export default Default;
解决方案
最简单有效的方法是绕过Next.js的路由系统,直接使用浏览器内置的History API:
'use client';
function ProductList() {
const openModal = () => {
// 不通过Next.js路由直接更改URL
history.pushState(null, '', `?modal=true`);
};
return (
<div>
<button onClick={openModal}>打开模态框</button>
</div>
);
}
使用Link
或useRouter
会触发对更改URL的导航,导致渲染逻辑重新运行。然而,使用History API只会更改URL而不通过Next的路由系统,所以渲染逻辑不会运行,但Next支持与useSearchParams
和usePathname
同步,允许检测URL变化并显示模态框。
结论
服务器组件是Next.js中重要的渲染优化策略,但如果使用不当,可能会导致意外的重新渲染或性能下降。要有效使用服务器组件,似乎很重要的是要很好地理解这些渲染策略并适当地利用它们。