Server Components
2026年9月6日
page.tsx、layout.tsx 默认都是服务端组件。它们在 Node 里执行,可以 fs、连数据库、读只有服务器才有的密钥。
import { readFile } from "node:fs/promises";
export default async function Page() {
const raw = await readFile("data.json", "utf8");
const data = JSON.parse(raw) as { name: string };
return <p>{data.name}</p>;
}
tsx
console.log 出现在终端。开发模式有时也会转发到浏览器控制台方便对,生产环境不要依赖这一点。
优点
- 秘密不过桥:API Key、数据库 URL 只在服务器出现
- 更瘦的 JS:这些模块不进客户端包
- 能碰系统:文件、SQL、内部 RPC 都可以
- 流式 HTML:配合
Suspense,算完一块推一块,FCP 更好
把取数写在组件里,少一层「专门为页面开的 BFF」,小项目很爽。连接池、事务、迁移该有的还是要有,别把 demo 查询直接当生产架构。
限制
服务端没有 DOM / BOM。useState、useEffect、onClick、window、document 都不能用。
JavaScript 平时说的三块:
| 部分 | 服务端 |
|---|---|
| ECMAScript(语法、Array、Promise) | 能用 |
| DOM | 不能 |
BOM(window、location) | 不能 |
下面这种会直接报错:
import { useState } from "react";
export default function Page() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}
tsx
有交互,拆一个客户端子组件。数据仍由服务端取,当作 props 传下去。
取数怎么写
export default async function Page() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 60 },
});
const posts = await res.json();
return (
<ul>
{posts.map((p: { id: string; title: string }) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
);
}
tsx
缓存、强制动态、connection() 这些放到后面「缓存策略」篇。这里先记住:服务端组件可以是 async 的,客户端组件不行。
下一篇:'use client' 之后发生了什么,以及为什么客户端不能再 import 服务端模块。