革命:async 组件
Next.js 13+ App Router 里,Server Component 可以直接是 async 函数:
async function PostList() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
⭐ 没 useEffect、没 useState、没 loading state——服务端跑完直接吐 HTML 给浏览器。
对比旧的 React 风格
// ❌ 老式(仍在 Client Component 用)
'use client';
import { useState, useEffect } from 'react';
function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/posts')
.then(r => r.json())
.then(setPosts)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{posts.map(p => <li>{p.title}</li>)}</ul>;
}
// ✓ 新式(Server Component)
async function PostList() {
const posts = await getPosts();
return <ul>{posts.map(p => <li>{p.title}</li>)}</ul>;
}
代码量减少 80%,且没有任何客户端 JS——HTML 直接生成完发给浏览器,SEO 完美。
直接连数据库
Server Component 直接跑在服务端 → 可以直接连数据库:
// src/lib/db.ts
import { Pool } from 'pg';
export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
// src/app/users/page.tsx
import { pool } from '@/lib/db';
export default async function UsersPage() {
const { rows } = await pool.query('SELECT * FROM users LIMIT 20');
return (
<ul>
{rows.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
没有 API endpoint,没有 fetch——服务端组件直接读 DB。
缓存(Next.js 15+ 起的新默认)
重要变化:Next.js 15 起,
fetch()默认不缓存——和原生 web fetch 行为一致。要缓存必须显式 opt-in。这与 Next.js 13/14 默认缓存的行为相反。
// 默认:每次请求都重新拉(等同 cache: 'no-store')
const data = await fetch('https://api.example.com/data');
// 显式强制缓存(构建期 + 运行期,相当于以前的默认)
fetch('https://api.example.com/data', { cache: 'force-cache' });
// 定时重新验证(每 60 秒)——也算缓存
fetch('https://api.example.com/data', { next: { revalidate: 60 } });
// 标签化(按需重新验证,配 revalidateTag)
fetch('https://api.example.com/data', { next: { tags: ['posts'], revalidate: 3600 } });
如果想给整个路由统一缓存策略,在 page / layout 顶部:
export const dynamic = 'force-static'; // 整页 SSG
// 或 'force-dynamic' 整页 SSR
标签按需 revalidate
// 在 Server Action 里触发
'use server';
import { revalidateTag } from 'next/cache';
export async function publishPost() {
await db.publish(...);
revalidateTag('posts'); // 所有打了 'posts' 标签的 fetch 重新拉
}
流式 + Suspense
慢数据用 Suspense 包:
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>Posts</h1>
<Suspense fallback={<p>Loading...</p>}>
<PostList />
</Suspense>
</div>
);
}
async function PostList() {
const posts = await getPostsSlowly(); // 慢
return <ul>{posts.map(...)}</ul>;
}
页面立刻显示 h1,PostList 加载完后流式补上——用户感知更快。
并行 fetch
// ❌ 顺序
async function Page() {
const user = await getUser(); // 等
const posts = await getPosts(); // 再等
// 总耗时 = user + posts
}
// ✓ 并行
async function Page() {
const [user, posts] = await Promise.all([
getUser(),
getPosts(),
]);
// 总耗时 = max(user, posts)
}
动态参数
// src/app/blog/[slug]/page.tsx
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`);
return res.json();
}
// Next.js 15+:params 是 Promise,必须 await
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
错误处理
async function PostList() {
try {
const posts = await getPosts();
return <ul>{posts.map(p => <li>{p.title}</li>)}</ul>;
} catch (err) {
return <p>Failed to load posts</p>;
}
}
或用 error.tsx 边界(上一篇讲过)。
何时还需要 Client Component fetch
Server Component 不能用的场景:
- 用户在浏览器交互后才知道要请求什么(搜索框 / 滚动加载 / 实时图)
- 需要
useState/useEffect/ 浏览器 API - 需要事件处理
这时用 Client Component + TanStack Query / SWR(react-advanced/16 篇细讲)。
坑
- Server Component 里不能用 useState / useEffect / onClick
- Next.js 15+
fetch默认不缓存——想要 SSG / ISR 必须显式cache: 'force-cache'或next: { revalidate } - Next.js 15+
params/searchParams是 Promise——必须await,别再当对象直接用 - 数据库连接不能在 Server Component 里反复建——用全局 pool(见上面例子)
- 大数据 / 实时数据用 Client + TanStack Query
下一篇:图片 + 静态资源。