服务器和客户端组件
¥Server and Client Components
默认情况下,布局和页面是 服务器组件,这允许你在服务器上获取数据并渲染部分 UI,可以选择缓存结果并将其流式传输到客户端。当你需要交互性或浏览器 API 时,可以使用 客户端组件 来分层功能。
¥By default, layouts and pages are Server Components, which lets you fetch data and render parts of your UI on the server, optionally cache the result, and stream it to the client. When you need interactivity or browser APIs, you can use Client Components to layer in functionality.
本页面解释了 Next.js 中的服务器和客户端组件的工作原理以及何时使用它们,并提供了如何在应用中将它们组合在一起的示例。
¥This page explains how Server and Client Components work in Next.js and when to use them, with examples of how to compose them together in your application.
何时使用服务器和客户端组件?
¥When to use Server and Client Components?
客户端和服务器环境的功能有所不同。服务器和客户端组件允许你根据用例在各个环境中运行逻辑。
¥The client and server environments have different capabilities. Server and Client components allow you to run logic in each environment depending on your use case.
在需要时使用客户端组件:
¥Use Client Components when you need:
状态 和 事件处理程序。例如
onClick
,onChange
.¥State and event handlers. E.g.
onClick
,onChange
.生命周期逻辑。例如
useEffect
。¥Lifecycle logic. E.g.
useEffect
.仅限浏览器的 API。例如
localStorage
、window
、Navigator.geolocation
等。¥Browser-only APIs. E.g.
localStorage
,window
,Navigator.geolocation
, etc.
在需要时使用服务器组件:
¥Use Server Components when you need:
从靠近源的数据库或 API 获取数据。
¥Fetch data from databases or APIs close to the source.
使用 API 密钥、令牌和其他密钥信息,但不要将它们暴露给客户端。
¥Use API keys, tokens, and other secrets without exposing them to the client.
减少发送到浏览器的 JavaScript 数量。
¥Reduce the amount of JavaScript sent to the browser.
改进 首次内容绘制 (FCP),并将内容逐步流式传输到客户端。
¥Improve the First Contentful Paint (FCP), and stream content progressively to the client.
例如,<Page>
组件是一个服务器组件,它获取帖子数据,并将其作为 props 传递给处理客户端交互的 <LikeButton>
。
¥For example, the <Page>
component is a Server Component that fetches data about a post, and passes it as props to the <LikeButton>
which handles client-side interactivity.
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
export default async function Page({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
return (
<div>
<main>
<h1>{post.title}</h1>
{/* ... */}
<LikeButton likes={post.likes} />
</main>
</div>
)
}
'use client'
import { useState } from 'react'
export default function LikeButton({ likes }: { likes: number }) {
// ...
}
服务器和客户端组件如何在 Next.js 中工作?
¥How do Server and Client Components work in Next.js?
在服务器上
¥On the server
在服务器上,Next.js 使用 React 的 API 来编排渲染。渲染工作被拆分成多个块,每个块由单独的路由段 (布局和页面) 组成:
¥On the server, Next.js uses React's APIs to orchestrate rendering. The rendering work is split into chunks, by individual route segments (layouts and pages):
服务器组件被渲染为一种称为 React 服务器组件 Payload(RSC Payload)的特殊数据格式。
¥Server Components are rendered into a special data format called the React Server Component Payload (RSC Payload).
客户端组件和 RSC 负载用于 prerender HTML。
¥Client Components and the RSC Payload are used to prerender HTML.
什么是 React 服务器组件负载 (RSC)?
¥What is the React Server Component Payload (RSC)?
RSC Payload 是渲染的 React Server 组件树的紧凑二进制表示。客户端上的 React 使用它来更新浏览器的 DOM。RSC Payload 包含:
¥The RSC Payload is a compact binary representation of the rendered React Server Components tree. It's used by React on the client to update the browser's DOM. The RSC Payload contains:
Server Components 的渲染结果
¥The rendered result of Server Components
客户端组件应渲染的位置的占位符以及对其 JavaScript 文件的引用
¥Placeholders for where Client Components should be rendered and references to their JavaScript files
从服务器组件传递到客户端组件的任何属性
¥Any props passed from a Server Component to a Client Component
在客户端(首次加载)
¥On the client (first load)
然后,在客户端上:
¥Then, on the client:
HTML 用于立即向用户显示快速的非交互式路由预览。
¥HTML is used to immediately show a fast non-interactive preview of the route to the user.
RSC Payload 用于协调客户端和服务器组件树。
¥RSC Payload is used to reconcile the Client and Server Component trees.
JavaScript 用于整合客户端组件并使应用具有交互性。
¥JavaScript is used to hydrate Client Components and make the application interactive.
什么是水合?
¥What is hydration?
Hydration 是 React 将 事件处理程序 附加到 DOM 的过程,以使静态 HTML 具有交互性。
¥Hydration is React's process for attaching event handlers to the DOM, to make the static HTML interactive.
后续导航
¥Subsequent Navigations
后续导航:
¥On subsequent navigations:
RSC 有效负载会被预取并缓存,以便即时导航。
¥The RSC Payload is prefetched and cached for instant navigation.
客户端组件完全在客户端渲染,不包含服务器渲染的 HTML。
¥Client Components are rendered entirely on the client, without the server-rendered HTML.
示例
¥Examples
使用客户端组件
¥Using Client Components
你可以通过在文件顶部(导入语句上方)添加 "use client"
指令来创建客户端组件。
¥You can create a Client Component by adding the "use client"
directive at the top of the file, above your imports.
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>{count} likes</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
"use client"
用于声明服务器和客户端模块图(树)之间的边界。
¥"use client"
is used to declare a boundary between the Server and Client module graphs (trees).
一旦文件被标记为 "use client"
,其所有导入和子组件都将被视为客户端包的一部分。这意味着你无需将该指令添加到每个用于客户端的组件中。
¥Once a file is marked with "use client"
, all its imports and child components are considered part of the client bundle. This means you don't need to add the directive to every component that is intended for the client.
减少 JS 包大小
¥Reducing JS bundle size
为了减小客户端 JavaScript 包的大小,请将 'use client'
添加到特定的交互式组件,而不是将 UI 的大部分内容标记为客户端组件。
¥To reduce the size of your client JavaScript bundles, add 'use client'
to specific interactive components instead of marking large parts of your UI as Client Components.
例如,<Layout>
组件主要包含静态元素,例如徽标和导航链接,但也包含一个交互式搜索栏。<Search />
具有交互性,需要作为客户端组件,但其余布局可以保留为服务器组件。
¥For example, the <Layout>
component contains mostly static elements like a logo and navigation links, but includes an interactive search bar. <Search />
is interactive and needs to be a Client Component, however, the rest of the layout can remain a Server Component.
'use client'
export default function Search() {
// ...
}
// Client Component
import Search from './search'
// Server Component
import Logo from './logo'
// Layout is a Server Component by default
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Logo />
<Search />
</nav>
<main>{children}</main>
</>
)
}
将数据从服务器传递到客户端组件
¥Passing data from Server to Client Components
你可以使用 props 将数据从服务器组件传递到客户端组件。
¥You can pass data from Server Components to Client Components using props.
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
export default async function Page({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
return <LikeButton likes={post.likes} />
}
'use client'
export default function LikeButton({ likes }: { likes: number }) {
// ...
}
或者,你可以使用 use
钩子 将数据从服务器组件流式传输到客户端组件。查看 example。
¥Alternatively, you can stream data from a Server Component to a Client Component with the use
Hook. See an example.
需要了解:传递给客户端组件的属性需要由 React 标记为 serializable。
¥Good to know: Props passed to Client Components need to be serializable by React.
交错服务器和客户端组件
¥Interleaving Server and Client Components
你可以将服务器组件作为属性传递给客户端组件。这允许你在客户端组件中以可视化的方式嵌套服务器渲染的 UI。
¥You can pass Server Components as a prop to a Client Component. This allows you to visually nest server-rendered UI within Client components.
一种常见的模式是使用 children
在 <ClientComponent>
中创建一个插槽。例如,<Cart>
组件从服务器获取数据,而 <Modal>
组件使用客户端状态来切换可见性。
¥A common pattern is to use children
to create a slot in a <ClientComponent>
. For example, a <Cart>
component that fetches data on the server, inside a <Modal>
component that uses client state to toggle visibility.
'use client'
export default function Modal({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
然后,在父服务器组件(例如 <Page>
)中,你可以将 <Cart>
作为 <Modal>
的子组件传递:
¥Then, in a parent Server Component (e.g.<Page>
), you can pass a <Cart>
as the child of the <Modal>
:
import Modal from './ui/modal'
import Cart from './ui/cart'
export default function Page() {
return (
<Modal>
<Cart />
</Modal>
)
}
在这种模式下,所有服务器组件都将提前在服务器上渲染,包括那些作为 props 的组件。生成的 RSC 负载将包含客户端组件在组件树中渲染位置的引用。
¥In this pattern, all Server Components will be rendered on the server ahead of time, including those as props. The resulting RSC payload will contain references of where Client Components should be rendered within the component tree.
上下文提供程序
¥Context providers
React 上下文 通常用于共享全局状态,例如当前主题。但是,服务器组件不支持 React 上下文。
¥React context is commonly used to share global state like the current theme. However, React context is not supported in Server Components.
要使用上下文,请创建一个接受 children
的客户端组件:
¥To use context, create a Client Component that accepts children
:
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext({})
export default function ThemeProvider({
children,
}: {
children: React.ReactNode
}) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
然后,将其导入服务器组件(例如 layout
):
¥Then, import it into a Server Component (e.g. layout
):
import ThemeProvider from './theme-provider'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}
你的服务器组件现在将能够直接渲染你的提供程序,并且你应用中的所有其他客户端组件都将能够使用此上下文。
¥Your Server Component will now be able to directly render your provider, and all other Client Components throughout your app will be able to consume this context.
需要了解:你应该在树中尽可能深地渲染提供程序 - 请注意
ThemeProvider
如何仅封装{children}
而不是整个<html>
文档。这使得 Next.js 可以更轻松地优化服务器组件的静态部分。¥Good to know: You should render providers as deep as possible in the tree – notice how
ThemeProvider
only wraps{children}
instead of the entire<html>
document. This makes it easier for Next.js to optimize the static parts of your Server Components.
第三方组件
¥Third-party components
当使用依赖于仅客户端功能的第三方组件时,你可以将其封装在客户端组件中,以确保其按预期工作。
¥When using a third-party component that relies on client-only features, you can wrap it in a Client Component to ensure it works as expected.
例如,可以从 acme-carousel
包导入 <Carousel />
。该组件使用 useState
,但还没有 "use client"
指令。
¥For example, the <Carousel />
can be imported from the acme-carousel
package. This component uses useState
, but it doesn't yet have the "use client"
directive.
如果你在客户端组件中使用 <Carousel />
,它将按预期工作:
¥If you use <Carousel />
within a Client Component, it will work as expected:
'use client'
import { useState } from 'react'
import { Carousel } from 'acme-carousel'
export default function Gallery() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(true)}>View pictures</button>
{/* Works, since Carousel is used within a Client Component */}
{isOpen && <Carousel />}
</div>
)
}
但是,如果你尝试在服务器组件中直接使用它,则会看到错误。这是因为 Next.js 不知道 <Carousel />
正在使用仅限客户端的功能。
¥However, if you try to use it directly within a Server Component, you'll see an error. This is because Next.js doesn't know <Carousel />
is using client-only features.
要解决此问题,你可以将依赖于仅客户端功能的第三方组件封装在你自己的客户端组件中:
¥To fix this, you can wrap third-party components that rely on client-only features in your own Client Components:
'use client'
import { Carousel } from 'acme-carousel'
export default Carousel
现在,你可以直接在服务器组件中使用 <Carousel />
:
¥Now, you can use <Carousel />
directly within a Server Component:
import Carousel from './carousel'
export default function Page() {
return (
<div>
<p>View pictures</p>
{/* Works, since Carousel is a Client Component */}
<Carousel />
</div>
)
}
给库作者的建议
¥Advice for Library Authors
如果你正在构建组件库,请将
"use client"
指令添加到依赖于仅客户端功能的入口点。这允许你的用户将组件导入服务器组件,而无需创建封装器。¥If you’re building a component library, add the
"use client"
directive to entry points that rely on client-only features. This lets your users import components into Server Components without needing to create wrappers.值得注意的是,一些打包程序可能会删除
"use client"
指令。你可以找到有关如何配置 esbuild 以在 React 封装平衡器 和 维塞尔分析 存储库中包含"use client"
指令的示例。¥It's worth noting some bundlers might strip out
"use client"
directives. You can find an example of how to configure esbuild to include the"use client"
directive in the React Wrap Balancer and Vercel Analytics repositories.
防止环境中毒
¥Preventing environment poisoning
JavaScript 模块可以在服务器和客户端组件模块之间共享。这意味着可能会意外将服务器专用代码导入客户端。例如,考虑以下函数:
¥JavaScript modules can be shared between both Server and Client Components modules. This means it's possible to accidentanlly import server-only code into the client. For example, consider the following function:
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
此函数包含一个永远不应暴露给客户端的 API_KEY
。
¥This function contains an API_KEY
that should never be exposed to the client.
在 Next.js 中,只有以 NEXT_PUBLIC_
为前缀的环境变量才会包含在客户端包中。如果变量没有前缀,Next.js 会将其替换为空字符串。
¥In Next.js, only environment variables prefixed with NEXT_PUBLIC_
are included in the client bundle. If variables are not prefixed, Next.js replaces them with an empty string.
结果,尽管 getData()
可以在客户端导入并执行,但它不会按预期工作。
¥As a result, even though getData()
can be imported and executed on the client, it won't work as expected.
为防止在客户端组件中意外使用,你可以使用 server-only
包。
¥To prevent accidental usage in Client Components, you can use the server-only
package.
npm install server-only
然后,将包导入包含仅服务器代码的文件中:
¥Then, import the package into a file that contains server-only code:
现在,如果你尝试将模块导入客户端组件,将会出现构建时错误。
¥Now, if you try to import the module into a Client Component, there will be a build-time error.
需要了解:相应的
client-only
包 可用于标记包含仅客户端逻辑的模块,例如访问window
对象的代码。¥Good to know: The corresponding
client-only
package can be used to mark modules that contain client-only logic like code that accesses thewindow
object.