Next.js 深度拆解:React 全栈框架的事实标准
posts posts 2026-07-11T02:50:00+08:00Next.js 是 React 全栈框架的事实标准,覆盖 SSR/SSG/ISR/RSC 四种渲染模型、App Router、Server Components。本文拆解其渲染模型演进、与传统 SPA/Remix/Astro 的取舍、生产部署路径。技术笔记nextjs, react, fullstack, ssr, rscNext.js 深度拆解:React 全栈框架的事实标准
核心判断
Next.js 之所以成为 React 全栈的事实标准,不是因为"React SSR 框架"——Remix、Astro、SvelteKit 都能做 SSR。它赢在三件事的组合:
- 四种渲染模型并存:SSR、SSG、ISR、RSC——同一个项目可以混用
- App Router + Server Components:让"哪些组件在服务端、哪些在客户端"成为代码级可声明的边界
- Vercel 平台深度集成:从代码到边缘部署的"5 分钟部署"是它的护城河
2024 年 App Router 转正 + Server Components 稳定后,Next.js 不再是"React 框架"——它变成了"Web 应用的运行时",覆盖渲染、数据获取、缓存、边缘函数、流式响应。
项目坐标
| 维度 | 数据 |
|---|---|
| 仓库 | vercel/next.js |
| Stars | 约 140k |
| 主语言 | JavaScript / TypeScript |
| License | MIT |
| 当前主版本 | 15.x(App Router 转正) |
| 部署平台 | Vercel(首选)、自托管 Node.js / Docker |
渲染模型演进
1. CSR(Client-Side Rendering)—— 传统 React
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user').then(r => r.json()).then(setUser);
}, []);
return <div>{user?.name}</div>;
}问题:
- SEO 极差(搜索引擎看不到内容)
- 首屏白屏(HTML 是空 shell)
- 服务端无法参与渲染
2. SSR(Server-Side Rendering)—— Next.js Pages Router
每个请求都在服务端渲染:
export async function getServerSideProps(ctx) {
const res = await fetch(`https://api.example.com/users/${ctx.params.id}`);
const user = await res.json();
return { props: { user } };
}适合:登录后内容、个性化页面。
3. SSG(Static Site Generation)—— 预渲染
构建时生成静态 HTML:
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return { paths: posts.map(p => ({ params: { id: p.id } })), fallback: false };
}适合:博客、文档、营销页。
4. ISR(Incremental Static Regeneration)—— 静态但可增量更新
export async function getStaticProps() {
const res = await fetch('https://api.example.com/posts');
return {
props: { posts: await res.json() },
revalidate: 60 // 60s 后重新生成
};
}适合:电商商品页、新闻列表——静态性能 + 内容更新。
5. RSC(React Server Components)—— App Router 的核心
服务端组件默认——不发送 JS 到浏览器:
// app/posts/page.tsx
async function PostsPage() {
const posts = await db.posts.findMany(); // 直接查数据库
return <PostList posts={posts} />;
}客户端组件只在你需要交互时:
'use client';
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>;
}RSC 的好处:
- 包体积大幅减小:服务端组件不发到浏览器
- 直接访问数据源:可以查数据库、调内部 API,不需要 public API
- 减少客户端 JS 执行:浏览器只 hydrate 必要的部分
- 流式渲染:服务端组件可以分块流式发送到浏览器(
<Suspense>边界)
App Router vs Pages Router
Next.js 13+ 默认 App Router(app/ 目录)。Pages Router(pages/ 目录)仍兼容。
| 维度 | App Router | Pages Router |
|---|---|---|
| 路由方式 | 文件夹即路由 | 文件即路由 |
| 默认渲染 | Server Component | Client Component |
| 数据获取 | async 组件函数 | getServerSideProps / getStaticProps |
| 布局 | 嵌套 layout.tsx | 单层 _app.tsx |
| Loading/Error | loading.tsx / error.tsx | 手动 |
| Streaming | 原生支持 | 有限 |
| 中间件 | 中间件 + 布局 | 中间件 |
| 学习曲线 | 较高(要理解 Server vs Client) | 较低 |
新项目应该用 App Router。Pages Router 进入维护期。
一个 App Router 示例
app/
├── layout.tsx # 根布局
├── page.tsx # 首页(Server Component)
├── posts/
│ ├── page.tsx # 列表
│ └── [id]/
│ └── page.tsx # 详情
├── api/
│ └── webhook/
│ └── route.ts # API 路由
└── components/
└── LikeButton.tsx # Client Component// app/posts/[id]/page.tsx
import { LikeButton } from '@/components/LikeButton';
import { db } from '@/lib/db';
import { notFound } from 'next/navigation';
export default async function PostPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const post = await db.posts.findUnique({ where: { id } });
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
<LikeButton postId={post.id} />
</article>
);
}// app/components/LikeButton.tsx
'use client';
import { useState } from 'react';
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>;
}// app/api/webhook/route.ts
export async function POST(req: Request) {
const body = await req.json();
// 处理 webhook
return Response.json({ ok: true });
}数据获取策略
App Router 推荐几种数据获取模式:
1. Server Component 直接 fetch
async function UserPage() {
const user = await fetch('https://api.example.com/me', {
headers: { cookie: cookies().toString() },
cache: 'no-store'
}).then(r => r.json());
return <div>{user.name}</div>;
}2. Server Actions(服务端操作)
// app/actions.ts
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.posts.create({ data: { title } });
revalidatePath('/posts'); // 刷新缓存
}// app/posts/new/page.tsx
import { createPost } from '@/app/actions';
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<button type="submit">Create</button>
</form>
);
}Server Actions 让"表单提交到服务端函数"成为一等公民——不需要单独写 API 路由。
3. Route Handlers(API 端点)
如果要给外部系统(移动 App、第三方)暴露 HTTP API:
// app/api/posts/route.ts
export async function GET() {
const posts = await db.posts.findMany();
return Response.json(posts);
}部署选项
| 平台 | 优势 | 限制 |
|---|---|---|
| Vercel | 零配置、全球边缘、自动 HTTPS、ISR/RSC 原生支持 | 商业锁定、价格 |
| 自托管 Node.js | 完全控制 | 需要自己配负载均衡、CDN |
| Docker | 跨云一致 | 同样要配 CDN |
| Cloudflare Pages + Workers | 边缘运行时 | 部分 Node.js API 不可用 |
| AWS Amplify | AWS 集成 | 配置较复杂 |
RSC + Server Actions 必须用 Node.js runtime(不能纯 Edge)——这是部署时的关键约束。
与 Remix / Astro / SvelteKit 的取舍
| 维度 | Next.js | Remix | Astro | SvelteKit |
|---|---|---|---|---|
| 学习曲线 | 中(要理解 RSC) | 中 | 低 | 低 |
| 渲染模型 | 4 种混用 | SSR-first | 静态/Island | 多模型 |
| 数据获取 | Server Component + Server Actions | loader/action | 多源 | load function |
| 路由 | 文件夹 | 文件夹 | 文件夹 | 文件夹 |
| 性能 | 中(hydration 开销) | 中 | 极高(少 JS) | 极高(少 JS) |
| 生态 | 巨大 | 中 | 中 | 中 |
| Vercel 集成 | 原生 | 需适配 | 适配 | 适配 |
决策建议:
- 大团队 + 复杂业务 + React 生态 → Next.js
- Web 标准优先 + 渐进增强 → Remix
- 内容站 + 极致性能 + 少 JS → Astro
- 不想用 React + 喜欢简洁 → SvelteKit
实战起步建议
- 新项目:用 App Router + TypeScript
- 数据访问:Server Component 直查(不绕道 API)
- 交互:仅在需要时用 Client Component(
'use client'边界尽量小) - 表单:优先用 Server Actions(而不是手写 fetch)
- 缓存:理解 Next.js 缓存层级(Request Memoization → Data Cache → Full Route Cache → Router Cache),按需
revalidate或cache: 'no-store' - 部署:Vercel 一键起步;后期要自托管用
next start
常见坑
1. Client / Server 边界混乱
'use client';
async function MyComponent() { // 错误:async 不能在 Client Component
const data = await fetch(...);
}2. 误把 secrets 暴露给客户端
// Server Component 里
const apiKey = process.env.SECRET_API_KEY;
fetch(`https://api.example.com?key=${apiKey}`); // OK:在服务端执行
// ❌ 把 apiKey 传给 Client Component
return <ClientComp apiKey={apiKey} />; // 会泄露
3. 缓存过期策略
默认 fetch 在 Server Component 里是缓存的(force-cache)。要实时数据用 cache: 'no-store',要定期刷新用 next: { revalidate: 60 }。
4. Streaming 边界
<Suspense> 包裹的部分会流式发送到浏览器,但 Suspense 边界外的部分仍要等所有内容准备好——要合理设计边界。
何时用 / 何时不用
适合:
- React 全栈应用 + SEO 要求
- 内部业务系统(dashboard、CRM、ERP)
- 营销站 + 博客 + 文档
- 大型团队的标准化框架
不适合:
- 单页工具(用 Vite + React SPA)
- 极致静态站(用 Astro 性能更好)
- 不用 React 的项目
参考资源
- 官方文档:https://nextjs.org/docs
- App Router 教程:https://nextjs.org/learn
- Server Components RFC:https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md
- 《Real-World Next.js》(Michele Riva 著)