服务器组件渲染策略

profile image

了解服务器组件的渲染策略和注意事项。同时探讨与Parallel Routes一起使用时可能出现的问题及其解决方案。

本帖由 Jetbrains's Coding Agent Junie junie logo翻译。如有任何翻译错误,请告知我们!

服务器组件的渲染

React的传统组件,即客户端组件,当其自身的**状态(state)**或父组件的状态发生变化时会重新渲染。然而,在服务器组件中不能使用钩子(hooks)。这意味着服务器组件没有状态。如下例所示,即使父组件的状态发生变化,服务器组件也不会重新渲染。那么,服务器组件何时会重新渲染呢?

server-component-rendering-strategies-example1

服务器组件渲染策略

根据Next.js官方文档,渲染过程采用三种策略:Static、Dynamic和Streaming。

Static Rendering

Static Rendering是Next.js的默认渲染策略。这种策略的核心特点是在构建时渲染一次组件,然后重复使用结果。

  • 构建时渲染
    • 服务器组件在应用程序构建时渲染
    • 渲染结果缓存在CDN中并提供给所有用户
  • 数据不变性
    • 一旦渲染的组件数据基本上不会改变
    • 即使刷新页面,也会显示相同的数据
    • 客户端状态变化不会影响服务器组件数据
  • 重新验证机制
    • 可以通过revalidateTag()revalidatePath()手动重新渲染
  1. 刷新屏幕时

    server-component-rendering-strategies-example2

    可以看到,即使更改状态并重新加载,数据也不会改变。

  2. 重新验证时

    server-component-rendering-strategies-example3

    可以看到,当执行Next.js的revalidateTag时,它会使服务器组件的缓存失效,重新获取数据并重新渲染。

Dynamic Rendering

Dynamic Rendering是Next.js的渲染策略,它为每个用户请求在服务器上重新渲染组件。

如果服务器组件满足以下两个条件之一,它会自动切换到Dynamic Rendering:

  1. 使用Dynamic API
  2. 在fetch选项中发现{ cache: 'no-store' }

渲染策略决策表

使用Dynamic API数据缓存渲染结果
❌ 否✅ 已缓存Static
✅ 是✅ 已缓存Dynamic
❌ 否❌ 未缓存Dynamic
✅ 是❌ 未缓存Dynamic

换句话说,只有在不使用Dynamic API并对fetch应用缓存时,才会进行Static Rendering。

Dynamic APIs

Dynamic API指的是依赖于请求时上下文的API。让我们看看主要的Dynamic API。

1. cookies()

typescript
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()

typescript
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

typescript
// app/page.tsx
export default function Page({
  searchParams,
}: {
  searchParams: { query: string };
}) {
  return <div>Search query: {searchParams.query}</div>;
}

与Parallel Routes一起使用时的注意事项

如下例所示,使用searchParams控制模态框时,可能会因Dynamic Rendering而出现问题:

  • searchParams变化时服务器组件会重新渲染
  • 数据获取导致模态框延迟
  • 模态框打开的同时数据变化
typescript
// 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;

server-component-rendering-strategies-example4

解决方案

最简单有效的方法是绕过Next.js的路由系统,直接使用浏览器内置的History API:

typescript
'use client';

function ProductList() {
  const openModal = () => {
    // 不通过Next.js路由直接更改URL
    history.pushState(null, '', `?modal=true`);
  };

  return (
    <div>
      <button onClick={openModal}>打开模态框</button>
    </div>
  );
}

使用LinkuseRouter会触发对更改URL的导航,导致渲染逻辑重新运行。然而,使用History API只会更改URL而不通过Next的路由系统,所以渲染逻辑不会运行,但Next支持与useSearchParamsusePathname同步,允许检测URL变化并显示模态框。

结论

服务器组件是Next.js中重要的渲染优化策略,但如果使用不当,可能会导致意外的重新渲染或性能下降。要有效使用服务器组件,似乎很重要的是要很好地理解这些渲染策略并适当地利用它们。