React Server Components(RSC)是React 18引入的组件渲染架构,将组件分为Server Components和Client Components两类。Server Components在服务端渲染,零JavaScript发送到客户端,适合数据获取和静态内容展示。Streaming SSR将页面分块流式发送到浏览器,配合Suspense实现渐进式渲染,首屏内容无需等待全部数据加载完成。这套架构在Next.js App Router中得到完整实现,显著提升Web性能优化的LCP和FCP指标。
React Server Components与Client Components的边界划分
Server Components运行在服务端,可以直连数据库、读取文件系统、使用服务端密钥,但不能使用useState、useEffect等客户端Hooks,也不能监听浏览器事件。Client Components通过文件顶部”use client”指令声明,运行在浏览器端,拥有完整React能力。
// app/products/page.tsx - Server Component(默认)
// 直接访问数据库,无需API层
import { db } from "@/lib/db"
export default async function ProductsPage() {
// 服务端直接查询数据库
const products = await db.product.findMany({
take: 20,
orderBy: { createdAt: "desc" }
})
return (
<div>
<h1>产品列表</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<h2>{product.name}</h2>
<p>{product.description}</p>
<span>¥{product.price}</span>
</li>
))}
</ul>
</div>
)
}
// app/components/AddToCartButton.tsx - Client Component
"use client"
import { useState } from "react"
export default function AddToCartButton({ productId }: { productId: number }) {
const [adding, setAdding] = useState(false)
const handleAdd = async () => {
setAdding(true)
await fetch("/api/cart", {
method: "POST",
body: JSON.stringify({ productId })
})
setAdding(false)
}
return (
<button onClick={handleAdd} disabled={adding}>
{adding ? "添加中..." : "加入购物车"}
</button>
)
}
Streaming SSR流式渲染与Suspense配合机制
传统SSR需要等待页面所有数据获取完成后,一次性将完整HTML发送到浏览器。Streaming SSR将页面拆分为多个chunk,每个chunk准备好就立即发送,浏览器可以在收到首个chunk时就开始渲染。
// app/dashboard/layout.tsx
import { Suspense } from "react"
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<nav>导航栏(立即渲染)</nav>
<main>
{/* 用户信息:优先加载,立即显示 */}
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile />
</Suspense>
{/* 订单列表:数据量大,慢加载,不阻塞首屏 */}
<Suspense fallback={<OrderSkeleton />}>
<OrderList />
</Suspense>
{/* 数据图表:最慢,用骨架屏占位 */}
<Suspense fallback={<ChartSkeleton />}>
<SalesChart />
</Suspense>
</main>
</body>
</html>
)
}
// 服务端异步组件,数据获取完成前Suspense显示fallback
async function OrderList() {
const orders = await fetch("https://api.example.com/orders", {
next: { revalidate: 60 } // ISR:60秒缓存
}).then(r => r.json())
return (
<table>
{orders.map(order => (
<tr key={order.id}>
<td>{order.id}</td>
<td>{order.status}</td>
<td>{order.total}</td>
</tr>
))}
</table>
)
}
Next.js App Router数据获取与缓存策略
fetch扩展与缓存控制
Next.js扩展了原生fetch,增加缓存控制选项:
// 强制缓存,永不过期(适合不常变的内容)
const data = await fetch(url, { cache: "force-cache" })
// 每次请求都重新获取(适合实时数据)
const data = await fetch(url, { cache: "no-store" })
// ISR:按时间间隔重新验证
const data = await fetch(url, { next: { revalidate: 3600 } }) // 1小时
// 按需重新验证
import { revalidateTag } from "next/cache"
// 获取时打标签
const data = await fetch(url, { next: { tags: ["products"] } })
// 数据更新后手动触发重新验证
revalidateTag("products")
并行与串行数据获取
// 并行获取(推荐):Promise.all同时请求
async function Dashboard() {
const [user, stats, notifications] = await Promise.all([
fetch("/api/user").then(r => r.json()),
fetch("/api/stats").then(r => r.json()),
fetch("/api/notifications").then(r => r.json())
])
return (
<>
<UserInfo user={user} />
<Stats data={stats} />
<Notifications items={notifications} />
</>
)
}
// 串行获取(有依赖关系时):配合Suspense避免瀑布效应
async function ProductDetail({ id }) {
return (
<>
<Suspense fallback={<Skeleton />}>
<ProductInfo id={id} />
</Suspense>
<Suspense fallback={<ReviewSkeleton />}>
<ProductReviews id={id} /> {/* 依赖ProductInfo中的productId */}
</Suspense>
</>
)
}
Server Actions:服务端操作的无API路由模式
Server Actions允许在Client Component中直接调用服务端函数,无需编写API路由:
// app/actions.ts
"use server"
import { db } from "@/lib/db"
import { revalidatePath } from "next/cache"
export async function createProduct(formData: FormData) {
const name = formData.get("name") as string
const price = parseFloat(formData.get("price") as string)
await db.product.create({
data: { name, price }
})
// 刷新页面缓存
revalidatePath("/products")
}
// app/products/create/page.tsx
import { createProduct } from "@/app/actions"
export default function CreateProductPage() {
return (
<form action={createProduct}>
<input name="name" type="text" placeholder="产品名称" />
<input name="price" type="number" placeholder="价格" />
<button type="submit">创建</button>
</form>
)
}
性能优化:RSC包体积缩减与加载策略
Server Components的JavaScript不会发送到客户端,页面中仅Client Component的代码被打包。通过分析打包体积可以验证效果:
# 分析客户端打包体积
npx @next/bundle-analyzer
# 关键指标:
# - First Load JS: 首屏加载的JS体积
# - 目标: 首屏JS < 200KB(gzip后)
动态导入Client Components可以进一步减小首屏包体积:
import dynamic from "next/dynamic"
// 按需加载,不阻塞首屏
const HeavyChart = dynamic(() => import("./HeavyChart"), {
loading: () => <div>加载图表...</div>,
ssr: false // 纯客户端渲染,不参与SSR
})
RSC架构下,数据获取在服务端完成,客户端无需额外请求。页面跳转时,Next.js通过RSC payload增量更新组件,只传输变化部分,实现接近原生App的页面切换体验。配合prefetch预取策略,常用路由的RSC payload在用户点击前就预加载完成。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-fu-wu-duan-zu-jian-yuan-li-yu/