Next.js Server Component 数据获取
Next.js App Router 的 Server Component 可以直接 await 数据源,在服务器读取 DB、内部服务或第三方 API,并把 UI-safe 结果传给页面与客户端组件。
[!info] related notes
- 所属 MOC:
- 前置概念:
- 并列概念:
- 易混淆概念:
- 关系笔记:
Next.js Server Component 数据获取
一句话定义
在 Next.js App Router 中,Server Component 可以在服务端直接执行异步数据获取:
export default async function Page() {
const data = await getData()
return <View data={data} />
}
它不需要为了“页面首次拿数据”而先在浏览器 mount,再通过 useEffect + useState 发起第一次请求。
Server Component 可以从哪里拿数据
Server Component
│
├─ internal data layer / DB
├─ owning service API
├─ third-party API
└─ server-only filesystem / secret-backed resource
数据源由架构 ownership 决定,而不是由组件语法决定。
当前 Next.js 应用自己拥有数据
Server Component
→ data/repository
→ ORM / SQL
→ DB
独立后端服务拥有数据
Server Component
→ Go / Java / Python API
→ Domain Service
→ DB
选择 DB 还是 API 继续看 数据所有权与服务边界。
为什么不默认 useEffect(fetch) 获取首屏数据
传统 Client Component:
"use client"
export function GoalsPage() {
const [goals, setGoals] = useState([])
useEffect(() => {
fetch('/api/goals')
.then(r => r.json())
.then(setGoals)
}, [])
return <GoalList goals={goals} />
}
首次链路通常需要:
HTML / JS
↓
download JS
↓
hydrate
↓
useEffect
↓
HTTP request
↓
setState
↓
render data
如果数据本来就是首屏必需,可以改成:
request
↓
Server Component awaits data
↓
server render / RSC result
↓
browser receives useful content
因此,useEffect 仍然是有价值的副作用工具,但不应被当作 Next.js 首屏 Server Data 的默认获取层。相关背景见 React SSR 数据获取位置。
直接 DB 的真正含义
Next.js 官方文档常展示 Server Component 直接查询数据库。这成立的前提是:当前应用有资格拥有这层数据访问。
可以安全执行 DB client
≠
应该绕过所有后端服务
如果 Go Backend 才是某领域的 owner,那么 Server Component 应调用 Go API,而不是直接连接它的数据库。
Server Component 与 Secret
Server Component 可以使用 server-only secret:
DATABASE_URL
service credential
third-party API secret
因为这些实现不会像 Client Component bundle 一样发送到浏览器。
但要区分:
Secret 不自动泄露
≠
查询结果自动安全
例如:
const user = await db.user.findUnique(...)
如果把完整实体直接传给 Client Component,其中的 passwordHash、内部 flags 或 private token 仍然可能被序列化出去。
因此应该建立 UI-safe projection:
DB / service entity
↓
select / mapper / DTO
↓
UI-safe data
↓
Server / Client Component
Server 获取数据,Client 负责交互
Client Component 不代表它必须自己 fetch 初始数据。
export default async function ProfilePage() {
const profile = await getProfile()
return <ProfileEditor initialProfile={profile} />
}
"use client"
export function ProfileEditor({ initialProfile }) {
const [name, setName] = useState(initialProfile.name)
// browser interaction
}
边界:
Initial data fetch
→ Server
Browser interaction
→ Client
这是一种非常常见的 App Router 组合方式。
与 TanStack Query 的关系
Server Component 更自然地解决:
页面进入时需要的 server data
TanStack Query 更自然地解决:
页面已经运行后,客户端如何缓存、refetch、retry、invalidate、optimistic update
因此二者不是互斥替代:
Server-rendered initial data
+
Client-managed server-state lifecycle
具体 client cache 语义见 TanStack Query 服务端状态管理。
BodySense 例子
Assessment Result
GET /assessment/123
↓
Next.js Server Component
↓
BodySense Go API
↓
Assessment domain logic
↓
UI-safe result
不应因为 Server Component 能直接访问 DB,就绕过 Go 的业务边界。
Consultation 初始历史
Page request
→ Server Component
→ Go API
→ existing conversation history
→ initial page
实时新事件则属于浏览器运行期:
Client Component
→ SSE
→ incremental runtime events
常见误区
“Client Component 必须自己 fetch”
错。Client Component 可以接收 Server Component 已经准备好的 props。
“Server Component 可以查 DB,所以 API 没用了”
错。独立服务 API 仍然承担 service ownership、权限与领域规则边界。
“只要在 Server Component 里 fetch,就一定更快”
不一定。真实性能还取决于请求并行、缓存、下游延迟和 Streaming。见 Request Waterfall 与并行数据获取。
最短记忆
首屏 server data
→ 优先 Server Component
数据源选择
→ 看 ownership
Client Component
→ 不等于必须 client fetch