渲染
服务器组件
React服务器组件允许您编写可以在服务器上呈现并可选择缓存的UI。 在Next.js中,渲染工作进一步分割为路由片段,以实现流式传输和部分渲染, 并有三种不同的服务器渲染策略:
本页面将介绍服务器组件的工作原理,何时可能使用它们以及不同的服务器渲染策略。
服务器渲染的好处
在服务器上进行渲染工作有一些好处,包括:
- 数据获取:服务器组件允许您将数据获取移到服务器上,靠近您的数据源。这可以通过减少用于渲染所需数据的获取时间以及客户端需要进行的请求次数来提高性能。
- 安全性:服务器组件允许您将敏感数据和逻辑保留在服务器上,例如令牌和API密钥,而无需将其风险暴露给客户端。
- 缓存:通过在服务器上进行渲染,结果可以被缓存并在后续请求和用户之间重复使用。这可以通过减少每个请求上的渲染和数据获取量来提高性能并减少成本。
- 捆绑大小:服务器组件允许您将以前可能影响客户端JavaScript捆绑大小的大型依赖项保留在服务器上。对于具有较慢互联网或较弱设备的用户来说,这是有益的,因为客户端不必下载、解析和执行服务器组件的任何JavaScript。
- 初始页面加载和首次内容绘制(FCP):在服务器上,我们可以生成HTML,使用户可以立即查看页面,而无需等待客户端下载、解析和 执行渲染页面所需的JavaScript。
- 搜索引擎优化和社交网络共享:渲染的HTML可以被搜索引擎爬虫用于索引您的页面,并且社交网络爬虫可以生成您页面的社交卡片预览。
- 流式传输:服务器组件允许您将渲染工作拆分成块并在准备就绪时将其流式传输到客户端。这使用户可以在无需等待整个页面在服务器上渲染完成的情况下更早地看到页面的部分。
在Next.js中使用服务器组件
默认情况下,Next.js使用服务器组件。 这使您可以在没有额外配置的情况下自动实现服务器渲染, 并且您可以在需要时选择使用客户端组件, 详见客户端组件。
服务器组件是如何渲染的?
在服务器上,Next.js使用React的API来编排渲染。 渲染工作被分成块:按独立的路由片段和Suspense边界。
每个块分两个步骤进行渲染:
- React将服务器组件渲染为一种称为React服务器组件负载(RSC Payload)的特殊数据格式。
- Next.js使用RSC Payload和客户端组件JavaScript指令来在服务器上渲染HTML。
然后,在客户端上:
- 使用HTML可以立即显示路由的快速非交互式预览 - 仅用于初始页面加载。
- 使用React服务器组件负载来协调客户端和服务器组件树,并更新DOM。
- 使用JavaScript指令来使客户端组件水合并使应用程序可交互。
什么是React服务器组件负载(RSC)?
RSC Payload是呈现的React服务器组件树的紧凑二进制表示。 它由React在客户端上使用以更新浏览器的DOM。RSC Payload包含:
- 服务器组件的渲染结果
- 应该呈现客户端组件的占位符以及对它们的JavaScript文件的引用
- 从服务器组件传递给客户端组件的任何props
服务器渲染策略
服务器渲染有三个子集: 静态、 动态和 流式。
静态渲染(默认)
使用静态渲染时,路由在构建时渲染, 或在数据重新验证 后在后台渲染。 结果被缓存,并可以推送到内容交付网络(CDN)。 这种优化允许您在用户和服务器请求之间共享渲染工作的结果。
静态渲染在路由具有不适用于用户的个性化的数据且可以在构建时得知的情况下非常有用, 例如静态博客文章或产品页面。
动态渲染
使用动态渲染时,为每个用户在请求时渲染路由。
动态渲染在路由具有适用于用户的个性化数据或只能在请求时得知的信息(例如cookies或URL的搜索参数)的情况下非常有用。
带有缓存数据的动态路由
在大多数网站中,路由既不是完全静态也不是完全动态 - 这是一个谱。 例如,您可以拥有一个电子商务页面,该页面使用已缓存的产品数据,定期重新验证,但同时也具有未缓存的个性化客户数据。
在Next.js中,可以拥有具有同时使用已缓存和未缓存数据的动态渲染路由。 这是因为RSC Payload和数据分别被缓存。 这使您可以选择动态渲染,而无需担心在请求时获取所有数据的性能影响。
切换到动态渲染
在渲染期间,如果发现动态函数 或未缓存的数据请求, Next.js将切换到动态渲染整个路由。 此表总结了动态函数和数据缓存对路由是静态还是动态渲染的影响:
动态函数 | 数据 | 路由 |
---|---|---|
否 | 已缓存 | 静态渲染 |
是 | 已缓存 | 动态渲染 |
否 | 未缓存 | 动态渲染 |
是 | 未缓存 | 动态渲染 |
在上表中,要使路由完全静态,必须缓存所有数据。 但是,您可以具有同时使用已缓存和未缓存数据获取的动态渲染路由。
作为开发人员,您无需在静态和动态渲染之间进行选择, 因为Next.js将根据所使用的功能和API自动为每个路由选择最佳渲染策略。 相反,您选择何时 缓存或重新验证特定数据, 并可能选择流式传递UI的部分。
动态函数
动态函数依赖于仅在请求时才能知道的信息,例如用户的cookies、当前请求标头或URL的搜索参数。 在Next.js中,这些动态函数包括:
cookies()
和headers()
:在服务器组件中使用这些函数将使整个路由在请求时切换到动态渲染。useSearchParams()
:- 在客户端组件中,它会跳过静态渲染,而是在客户端上渲染到最近的父
Suspense
边界上的所有客户端组件。 - 我们建议将使用
useSearchParams()
的客户端组件包装在<Suspense/>
边界中。这将允许其上面的任何客户端组件被静态渲染。示例。
- 在客户端组件中,它会跳过静态渲染,而是在客户端上渲染到最近的父
searchParams
:使用Pages prop将页面切换到在请求时动态渲染。 使用这些函数之一将使整个路由在请求时切换到动态渲染。
流式传输
流式传输使您能够从服务器逐步呈现UI。工作被分成块,并在准备就绪时流式传输到客户端。 这使用户可以在整个内容完成渲染之前立即看到页 面的部分。
流式传输默认内置到Next.js App Router中。 这有助于改善初始页面加载性能,以及依赖于较慢数据获取的UI,该数据获取会阻止整个路由的渲染, 例如产品页面上的评论。
您可以使用loading.js
和具有React Suspense
的UI组件开始流式]传输路由片段。
有关更多信息,请参见Loading UI and Streaming部分。
客户端组件
客户端组件允许您编写可以在请求时在客户端上呈现的交互式UI。 在Next.js中,客户端渲染是可选择的(opt-in), 这意味着您必须明确决定React应在客户端上渲染哪些组件。
本页面将介绍客户端组件的工作原理、它们是如何渲染的以及何时使用它们。
客户端渲染的好处
在客户端进行渲染工作有一些好处,包括:
- 互动性:客户端组件可以使用 状态、效果和事件侦听器,这意味着它们可以为用户提供即时反馈并更新UI。
- 浏览器API:客户端组件可以访问浏览器API, 如地理位置或 localStorage,使您能够为特定用例构建UI。
在Next.js中使用客户端组件
要使用客户端组件,
您可以在文件的顶部(在导入之前)添加React的"use client"
指令。
"use client"
用于声明服务器和客户端组件模块之间的边界。
这意味着通过在文件中定义"use client"
,导入到该文件的所有其他模块,包括子组件,都被视为客户端捆绑的一部分。
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
下面的图示显示,如果未定义"use client"
指令,则在嵌套组件(toggle.js
)中使用onClick
和useState
会导致错误。
这是因为默认情况下,在服务器上渲染组件,这些API是不可用的。
通过在toggle.js
中定义"use client"
指令,
您可以告诉React在客户端上渲染组件及其子组件,其中这些API是可用的。
定义多个"use client"
入口点:
您可以在React组件树中定义多个"use client"入口点。这允许您将应用程序拆分为多个客户端捆绑包(或分支)。
但是,并不需要在每个需要在客户端上渲染的组件中定义"use client"。一旦定义了边界,所有子组件和导入到其中的模块都被视为客户端捆绑的一部分。
客户端组件是如何渲染的?
在Next.js中,客户端组件的渲染方式取决于请求是属于完整页面加载(对应应用程序的初始访问或浏览器刷新触发的页面重新加载)还是后续导航的一部分。
完整页面加载
为了优化初始页面加载,Next.js将使用React的API在服务器上为客户端和服务器组件渲染静态HTML预览。这意味着当用户首次访问您的应用程序时,他们将立即看到页面的内容,而无需等待客户端下载、解析和执行客户端组件JavaScript捆绑包。
在服务器上:
- React将服务器组件渲染为一种称为React服务器组件负载(RSC Payload)的特殊数据格式,其中包括对客户端组件的引用。
- Next.js使用RSC Payload和客户端组件JavaScript指令在服务器上为路由渲染HTML。
然后,在客户端上:
- 使用HTML可以立即显示路由的快速非交互式初始预览。
- 使用React服务器组件负载来协调客户端和服务器组件树,并更新DOM。
- 使用JavaScript指令来使客户端组件水合 并使其UI可交互。
什么是水合?
水合是将事件侦听器附加到DOM的过程,以使静态HTML变得可交互。在幕后,
水合是使用hydrateRoot
React API完成的。
后续导航
在后续导航中,客户端组件完全在客户端上渲染,无需服务器渲染的HTML。
这意味着将下载并解析客户端组件JavaScript捆绑包。 一旦捆绑包准备好,React将使用RSC Payload来协调客户端和服务器组件树,并更新DOM。
返回服务器环境
有时,在声明了"use client"边界之后,您可能希望回到服务器环境。 例如,您可能希望减小客户端捆绑包大小、在 服务器上获取数据或使用仅在服务器上可用的API。
即使嵌套在客户端组件内,您仍然可以将代码保留在服务器上,方法是交错使用客户端和服务器组件以及 服务器操作。 有关更多信息, 请参见Composition Patterns页面。
服务器和客户端组合模式
在构建React应用程序时,您需要考虑应该在服务器端还是客户端端渲染应用程序的哪些部分。 本页面涵盖了在使用服务器组件和客户端组件时的一些推荐组合模式。
何时使用服务器组件和客户端组件?
以下是服务器组件和客户端组件的不同用例的快速摘要:
要执行的操作是什么? | 服务器组件 | 客户端组件 |
---|---|---|
获取数据 | ✅ | ❌ |
访问后端资源(直接) | ✅ | ❌ |
在服务器上保存敏感信息(访问令牌、API密钥等) | ✅ | ❌ |
将大 型依赖项保留在服务器上/减少客户端JavaScript | ✅ | ❌ |
添加互动性和事件侦听器(onClick()、onChange()等) | ❌ | ✅ |
使用状态和生命周期效果(useState()、useReducer()、useEffect()等) | ❌ | ✅ |
使用仅浏览器API | ❌ | ✅ |
使用依赖于状态、效果或仅浏览器API的自定义挂钩 | ❌ | ✅ |
使用React类组件 | ❌ | ✅ |
服务器组件模式
在选择客户端渲染之前,您可能希望在服务器上执行一些工作,比如获取数据或访问数据库或后端服务。
在处理服务器组件时,以下是一些常见的模式:
在组件之间共享数据
在服务器上获取数据时,可能存在需要在不同组件之间共享数据的情况。 例如,您可能有一个布局和一个依赖于相同数据的页面。
您可以使用fetch或React的cache函数,在组件中获取相同的数据,而无需担心为相同数据发出重复请求,
而不是使用React Context
(在服务器上不可用)或将数据传递为props。
这是因为React会扩展fetch
以自动记忆数据请求,当fetch
不可用时,可以使用cache