React Server Components(RSC)是React 18引入的服务端组件架构,将组件渲染逻辑拆分为服务端和客户端两个层级。服务端组件在服务器上执行,直接访问数据库和文件系统,零客户端JS开销;客户端组件保留交互能力,按需水合。Streaming SSR将HTML分块流式推送,首屏内容在数据加载完成前即可渲染。本文从RSC架构原理到Next.js App Router实战配置,拆解服务端组件与流式渲染的工程实现。
React Server Components架构原理
RSC将组件分为两类:Server Components默认在服务端渲染,不能使用useState、useEffect等客户端API,但可以直接读取数据库、访问文件系统、调用服务端SDK;Client Components通过'use client'指令声明,拥有完整的客户端交互能力。两者可以嵌套使用:Server Component可以导入Client Component,Client Component可以通过children props接收Server Component。
// app/page.tsx - Server Component(默认)
import { db } from '@/lib/db'
import { ProductCard } from '@/components/ProductCard'
import { AddToCartButton } from '@/components/AddToCartButton'
// 直接在服务端查询数据库,无需API层
async function getProducts() {
return db.product.findMany({
where: { status: 'active' },
take: 20,
orderBy: { createdAt: 'desc' },
})
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<main>
<h1>商品列表</h1>
{products.map(product => (
<div key={product.id}>
{/* Server Component: 零客户端JS */}
<ProductCard product={product} />
{/* Client Component: 交互能力 */}
<AddToCartButton productId={product.id} />
</div>
))}
</main>
)
}
// components/AddToCartButton.tsx
'use client' // 声明为客户端组件
import { useState } from 'react'
import { addToCart } from '@/lib/cart'
export function AddToCartButton({ productId }: { productId: string }) {
const [loading, setLoading] = useState(false)
const handleClick = async () => {
setLoading(true)
await addToCart(productId)
setLoading(false)
}
return (
<button onClick={handleClick} disabled={loading}>
{loading ? '添加中...' : '加入购物车'}
</button>
)
}
组件嵌套规则与数据传递约束
RSC的嵌套有严格规则:Server Component可以将Server Component作为子组件传递给Client Component,但不能直接导入Server Component到Client Component中。这种限制通过children props实现——Client Component接收的children在服务端渲染后以序列化形式传递。
// ✅ 正确:Server Component通过children传递Server Component给Client Component
import { ClientWrapper } from '@/components/ClientWrapper'
import { ServerItem } from '@/components/ServerItem'
export default async function Page() {
const data = await fetchData()
return (
<ClientWrapper>
{data.map(item => (
<ServerItem key={item.id} data={item} />
))}
</ClientWrapper>
)
}
// ❌ 错误:Client Component不能直接导入Server Component
// 'use client'
// import { ServerItem } from '@/components/ServerItem' // 编译报错
Server Component返回的数据必须是可序列化的——不能传递函数、Class实例、Symbol等。对象、数组、字符串、数字、Date等基本类型和plain object可以正常传递。
Streaming SSR与Suspense流式渲染配置
Streaming SSR将页面HTML拆分为多个chunk,服务器在每个chunk准备好后立即发送给浏览器,浏览器可以在等待后续数据的同时开始渲染已到达的内容。React的<Suspense>组件是流式渲染的边界标记,被Suspense包裹的异步组件在数据未就绪时展示fallback内容,数据就绪后流式替换。
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { SalesChart } from '@/components/SalesChart'
import { UserTable } from '@/components/UserTable'
import { StatsCards } from '@/components/StatsCards'
export default function DashboardPage() {
return (
<main>
<h1>数据看板</h1>
{/* 快速渲染:无数据依赖 */}
<Suspense fallback={<div>加载统计卡片...</div>}>
<StatsCards />
</Suspense>
{/* 流式渲染:数据就绪后替换fallback */}
<Suspense fallback={<ChartSkeleton />}>
<SalesChart />
</Suspense>
{/* 嵌套Suspense:独立流式渲染 */}
<Suspense fallback={<TableSkeleton />}>
<UserTable />
</Suspense>
</main>
)
}
// 独立的loading.tsx文件也可以定义Suspense边界
// app/dashboard/loading.tsx
export default function Loading() {
return (
<div className="animate-pulse">
<div className="h-32 bg-gray-200 rounded mb-4" />
<div className="h-64 bg-gray-200 rounded" />
</div>
)
}
Next.js的App Router自动为每个路由段生成Suspense边界。当多个异步组件同时挂起时,它们各自独立流式渲染,不会互相阻塞。这意味着首屏可以立即展示页面框架和已就绪内容,慢组件在后台加载完成后逐步填充。
数据获取策略与缓存控制
RSC中的数据获取使用fetch API的扩展缓存配置。Next.js扩展了原生fetch,增加next.revalidate和next.tags参数实现ISR(Incremental Static Regeneration)和按需重新验证。
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
import { revalidateTag } from 'next/cache'
// ISR:每60秒重新生成
async function getPost(slug: string) {
const res = await fetch(`https://api.yunthe.com/posts/${slug}`, {
next: { revalidate: 60, tags: [`post-${slug}`] }
})
if (!res.ok) notFound()
return res.json()
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
// 按需重新验证:发布新内容时触发
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const { slug } = await request.json()
revalidateTag(`post-${slug}`)
return NextResponse.json({ revalidated: true })
}
构建优化与Bundle分析
RSC的核心优势之一是减少客户端JS体积。Server Component的代码不会打包到客户端bundle中,只有Client Component的代码会发送到浏览器。通过Bundle Analyzer可以量化每个页面的客户端JS开销。
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
reactStrictMode: true,
experimental: {
optimizePackageImports: ['lucide-react', 'date-fns'],
},
// 服务端组件的模块排除
serverExternalPackages: ['@prisma/client', 'sharp'],
})
// package.json
{
"scripts": {
"analyze": "ANALYZE=true next build"
}
}
React Server Components通过服务端渲染消除不必要的客户端JS开销,Streaming SSR让首屏内容在数据加载完成前即可呈现。组件嵌套规则确保服务端逻辑不会泄露到客户端,Suspense边界实现细粒度的流式渲染控制。数据获取的缓存策略在静态性能和实时性之间提供灵活配置,Bundle分析帮助持续优化客户端体积。这套架构在内容密集型应用中优势显著——商品列表、文章详情、数据看板等场景可以大幅降低首屏加载时间和客户端资源消耗。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-fu-wu-duan-zu-jian-jia-gou-yu/