目录

Next.js 深度拆解:React 全栈框架的事实标准

Next.js 深度拆解:React 全栈框架的事实标准

核心判断

Next.js 之所以成为 React 全栈的事实标准,不是因为"React SSR 框架"——Remix、Astro、SvelteKit 都能做 SSR。它赢在三件事的组合

  1. 四种渲染模型并存:SSR、SSG、ISR、RSC——同一个项目可以混用
  2. App Router + Server Components:让"哪些组件在服务端、哪些在客户端"成为代码级可声明的边界
  3. Vercel 平台深度集成:从代码到边缘部署的"5 分钟部署"是它的护城河

2024 年 App Router 转正 + Server Components 稳定后,Next.js 不再是"React 框架"——它变成了"Web 应用的运行时",覆盖渲染、数据获取、缓存、边缘函数、流式响应。

项目坐标

维度数据
仓库vercel/next.js
Stars约 140k
主语言JavaScript / TypeScript
LicenseMIT
当前主版本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 RouterPages Router
路由方式文件夹即路由文件即路由
默认渲染Server ComponentClient Component
数据获取async 组件函数getServerSideProps / getStaticProps
布局嵌套 layout.tsx单层 _app.tsx
Loading/Errorloading.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 AmplifyAWS 集成配置较复杂

RSC + Server Actions 必须用 Node.js runtime(不能纯 Edge)——这是部署时的关键约束。

与 Remix / Astro / SvelteKit 的取舍

维度Next.jsRemixAstroSvelteKit
学习曲线中(要理解 RSC)
渲染模型4 种混用SSR-first静态/Island多模型
数据获取Server Component + Server Actionsloader/action多源load function
路由文件夹文件夹文件夹文件夹
性能中(hydration 开销)极高(少 JS)极高(少 JS)
生态巨大
Vercel 集成原生需适配适配适配

决策建议

  • 大团队 + 复杂业务 + React 生态 → Next.js
  • Web 标准优先 + 渐进增强 → Remix
  • 内容站 + 极致性能 + 少 JS → Astro
  • 不想用 React + 喜欢简洁 → SvelteKit

实战起步建议

  1. 新项目:用 App Router + TypeScript
  2. 数据访问:Server Component 直查(不绕道 API)
  3. 交互:仅在需要时用 Client Component('use client' 边界尽量小)
  4. 表单:优先用 Server Actions(而不是手写 fetch)
  5. 缓存:理解 Next.js 缓存层级(Request Memoization → Data Cache → Full Route Cache → Router Cache),按需 revalidatecache: 'no-store'
  6. 部署: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 的项目

参考资源