Skip to content

Server Components

🙋 Server Components 是 App Router 的核心特性。组件在服务端执行意味着什么?能带来哪些优势?

什么是 Server Components#

React Server Components (RSC) 是一种新的组件类型,在服务器上执行并渲染,而不是在浏览器中。

核心特点#

特性Server ComponentsClient Components
执行环境服务器浏览器
JavaScript不发送到客户端打包发送到客户端
数据获取直接访问后端资源通过 API 获取
状态/交互❌ 不支持✅ 支持
生命周期❌ 不支持✅ 支持

默认行为#

在 App Router 中,组件默认是 Server Components

// app/page.tsx - 这是 Server Component
// Next.js 15.x
export default function HomePage() {
console.log('这行日志在服务器终端输出')
return <h1>Hello from Server</h1>
}

Server Components 优势#

1. 减少客户端 JavaScript#

app/page.tsx
// Next.js 15.x
// 这些依赖不会发送到客户端
import { marked } from 'marked'
import hljs from 'highlight.js'
async function getContent() {
const res = await fetch('https://api.example.com/content')
return res.json()
}
export default async function Page() {
const content = await getContent()
// 在服务端处理 Markdown
const html = marked(content.markdown, {
highlight: (code, lang) => hljs.highlight(code, { language: lang }).value,
})
return <article dangerouslySetInnerHTML={{ __html: html }} />
}

🎯 效果markedhighlight.js 的代码不会打包到客户端,减少 bundle 体积。

2. 直接访问后端资源#

app/users/page.tsx
// Next.js 15.x
import { db } from '@/lib/db'
export default async function UsersPage() {
// 直接查询数据库
const users = await db.user.findMany({
select: { id: true, name: true, email: true },
})
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
app/files/page.tsx
// Next.js 15.x
import { readFile } from 'fs/promises'
import { join } from 'path'
export default async function FilesPage() {
// 直接读取文件系统
const content = await readFile(
join(process.cwd(), 'data', 'config.json'),
'utf-8'
)
const config = JSON.parse(content)
return <pre>{JSON.stringify(config, null, 2)}</pre>
}

3. 敏感信息安全#

app/admin/page.tsx
// Next.js 15.x
export default async function AdminPage() {
// API 密钥不会暴露给客户端
const response = await fetch('https://api.stripe.com/v1/charges', {
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
},
})
const charges = await response.json()
return <div>{/* 显示数据 */}</div>
}

4. 更好的初始加载#

// Server Component 的 HTML 可以立即显示
// 不需要等待 JavaScript 加载和执行
export default async function Page() {
const data = await getData()
// 用户立即看到完整 HTML
return <div>{data.content}</div>
}

异步数据获取#

Server Components 可以是异步函数:

基础用法#

app/posts/page.tsx
// Next.js 15.x
interface Post {
id: number
title: string
body: string
}
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
if (!res.ok) {
throw new Error('Failed to fetch posts')
}
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<ul className="space-y-4">
{posts.slice(0, 10).map((post) => (
<li key={post.id} className="border-b pb-4">
<h2 className="font-bold">{post.title}</h2>
<p className="text-gray-600">{post.body}</p>
</li>
))}
</ul>
)
}

并行数据获取#

app/dashboard/page.tsx
// Next.js 15.x
async function getUser() {
const res = await fetch('https://api.example.com/user')
return res.json()
}
async function getStats() {
const res = await fetch('https://api.example.com/stats')
return res.json()
}
async function getNotifications() {
const res = await fetch('https://api.example.com/notifications')
return res.json()
}
export default async function DashboardPage() {
// 并行获取数据
const [user, stats, notifications] = await Promise.all([
getUser(),
getStats(),
getNotifications(),
])
return (
<div>
<h1>欢迎, {user.name}</h1>
<div>访问量: {stats.visits}</div>
<div>通知: {notifications.length}</div>
</div>
)
}

瀑布流避免#

// ❌ 瀑布流:顺序请求,效率低
async function Page() {
const user = await getUser() // 1s
const posts = await getPosts() // 1s
const comments = await getComments() // 1s
// 总计 3s
}
// ✅ 并行:同时请求
async function Page() {
const [user, posts, comments] = await Promise.all([
getUser(),
getPosts(),
getComments(),
])
// 总计 ~1s
}

组件组合#

Server 包含 Server#

app/layout.tsx
// Next.js 15.x
import { Header } from '@/components/Header'
import { Footer } from '@/components/Footer'
// Server Component 可以导入其他 Server Component
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<Header /> {/* Server Component */}
{children}
<Footer /> {/* Server Component */}
</>
)
}

Server 包含 Client#

// app/page.tsx - Server Component
// Next.js 15.x
import { InteractiveButton } from '@/components/InteractiveButton'
async function getData() {
return { message: 'Hello' }
}
export default async function Page() {
const data = await getData()
return (
<div>
<h1>{data.message}</h1>
{/* Client Component 作为子组件 */}
<InteractiveButton />
</div>
)
}
// components/InteractiveButton.tsx - Client Component
'use client'
import { useState } from 'react'
export function InteractiveButton() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>点击次数: {count}</button>
}

传递数据给 Client#

// app/page.tsx - Server Component
// Next.js 15.x
import { ProductList } from '@/components/ProductList'
async function getProducts() {
const res = await fetch('https://api.example.com/products')
return res.json()
}
export default async function Page() {
// 在服务端获取数据
const products = await getProducts()
// 传递给 Client Component
return <ProductList initialProducts={products} />
}
// components/ProductList.tsx - Client Component
'use client'
import { useState } from 'react'
interface Product {
id: string
name: string
price: number
}
interface Props {
initialProducts: Product[]
}
export function ProductList({ initialProducts }: Props) {
const [products, setProducts] = useState(initialProducts)
const [filter, setFilter] = useState('')
const filtered = products.filter((p) =>
p.name.toLowerCase().includes(filter.toLowerCase())
)
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="搜索产品..."
/>
<ul>
{filtered.map((product) => (
<li key={product.id}>
{product.name} - ¥{product.price}
</li>
))}
</ul>
</div>
)
}

🔶 注意:传递给 Client Component 的 props 必须是可序列化的(不能传递函数、类实例等)。

使用限制#

不能使用的功能#

// ❌ Server Component 不支持
// 1. React Hooks
import { useState, useEffect } from 'react'
export default function Page() {
const [state, setState] = useState(0) // 错误!
}
// 2. 浏览器 API
export default function Page() {
window.localStorage.getItem('key') // 错误!
}
// 3. 事件处理
export default function Page() {
return <button onClick={() => {}}>点击</button> // 错误!
}

何时需要 Client Component#

最佳实践#

1. 尽可能使用 Server Components#

app/page.tsx
// ✅ 好:只在需要交互的地方使用 Client
import { StaticContent } from '@/components/StaticContent'
import { InteractiveWidget } from '@/components/InteractiveWidget'
export default function Page() {
return (
<div>
<StaticContent /> {/* Server */}
<InteractiveWidget /> {/* Client */}
</div>
)
}

2. 将 Client 边界推到叶子节点#

// ❌ 差:整个组件是 Client
'use client'
export default function Page() {
const [open, setOpen] = useState(false)
return (
<div>
<h1>标题</h1>
<p>大量静态内容...</p>
<button onClick={() => setOpen(!open)}>切换</button>
{open && <Modal />}
</div>
)
}
// ✅ 好:只有按钮是 Client
// page.tsx (Server)
import { ToggleButton } from './ToggleButton'
export default function Page() {
return (
<div>
<h1>标题</h1>
<p>大量静态内容...</p>
<ToggleButton />
</div>
)
}
// ToggleButton.tsx (Client)
;('use client')
export function ToggleButton() {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(!open)}>切换</button>
{open && <Modal />}
</>
)
}

3. 使用 children 模式#

components/ClientWrapper.tsx
'use client'
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) return null
return <div>{children}</div>
}
app/page.tsx
import { ClientWrapper } from '@/components/ClientWrapper'
import { ServerContent } from '@/components/ServerContent'
export default function Page() {
return (
<ClientWrapper>
{/* Server Component 作为 children 传入 */}
<ServerContent />
</ClientWrapper>
)
}

常见问题#

🤔 Q: 如何判断组件是 Server 还是 Client?

🤔 Q: Server Component 可以调用 API 吗?

可以直接用 fetch,不需要额外的 API 路由。

🤔 Q: Server Component 的错误如何处理?

使用 error.tsx 文件创建错误边界。


下一篇将介绍 Client Components,深入理解 "use client" 指令和交互式组件的开发。

-EOF-