发布日期

第 01 讲|Next.js 是什么:从「React 框架」到「全栈运行时」

理解 Next.js 从「React 框架」到「全栈运行时」的定位演进,建立整体架构认知

阶段一:框架认知与源码导航 · 第 1 / 40 讲 难度:⭐⭐ · 预计耗时:1.5–2 小时

学习目标

  • 能用一句话描述 Next.js 解决了 React 的哪 5 个痛点。
  • 能说清 Pages Router 与 App Router 的差异、以及二者今天的取舍。
  • 能在自己脑中画出 Next.js 的「五件套架构」(路由、渲染、构建、运行时、数据)。
  • 跑通一个最小的 App Router demo,看到 RSC payload。

1. Next.js 到底是什么

React 官方文档里有一句话:"React 只是 UI 层"。但要把 React 变成一个生产可用的 Web 应用,你至少需要:

  1. 路由(routing):URL → 组件树。
  2. 渲染策略(SSR / SSG / ISR / RSC):什么时候渲染、在哪里渲染。
  3. 数据获取(data fetching / mutations):组件如何拿到数据、如何更新数据。
  4. 构建产物(bundling / code splitting / asset optimization):把代码变成可下发的 JS / CSS / 图像。
  5. 运行时(Node / Edge / Browser):在哪里执行那些"非 React"的代码(中间件、API、Image 优化等)。

Next.js 就是把这 5 件事打包成一个开箱即用的"全栈框架"——它不只是 React 的脚手架,而是一个完整的 HTTP 服务 + 构建器 + 客户端 runtime

与 Remix / Nuxt / SvelteKit 等竞品相比,Next.js 当前最大的差异是:深度押注 React Server Components (RSC) 与 Partial Prerendering (PPR),把"哪些代码在服务器执行"作为框架的核心抽象。

2. Pages Router vs App Router:分水岭

Next.js 同时支持两套路由系统。

维度Pages Router (pages/)App Router (app/)
路由约定pages/[name].tsx 一个文件 = 一个路由app/[name]/page.tsx,文件夹 + 多个约定文件
默认组件类型客户端组件(React 18 之前的模型)Server Components
数据获取getStaticProps / getServerSideProps / getStaticPaths直接在 Server Component 中 await fetch(...)
Layout全局 _app.tsx + 手动嵌套layout.tsx 文件嵌套、并行路由、intercepting route
MutationAPI Route + 客户端 fetchServer Actions'use server'
流式渲染不支持Streaming SSR + RSC payload 流
缓存模型路由级(ISR)4 层缓存(Request / Data / Full Route / Router)
状态稳定维护中官方主推的未来方向

源码层面,二者甚至共用同一个 base server,但走不同的"route module":

33:packages/next/src/server/next-server.ts
import type { AppPageModule } from './route-modules/app-page/module'
import type { AppRouteModule } from './route-modules/app-route/module.compiled'
import type { ErrorModule } from './load-default-error-components'
import type { PagesModule } from './route-modules/pages/module.compiled'

route-modules/ 下有四种 module:app-page / app-route / pages / pages-api,分别对应 App Router 的页面、App Router 的 Route Handler、Pages Router 的页面、Pages Router 的 API。这是理解整个框架的"四象限"。

本课程默认聚焦 App Router——Pages Router 只在涉及历史包袱、迁移时提及。

3. Next.js 的「五件套架构」

把仓库源码摊开看,整个框架可以拆成 5 个层次:

┌───────────────────────────────────────────────────────┐
│  ① CLI / Entry        next-dev / next-build / next-start
│  packages/next/src/cli/
├───────────────────────────────────────────────────────┤
│  ② Build              webpack / SWC / Turbopack
│  packages/next/src/build/
├───────────────────────────────────────────────────────┤
│  ③ Server Runtime     base-server / next-server / dev-server / edge-adapter
│  packages/next/src/server/
├───────────────────────────────────────────────────────┤
│  ④ Render Pipeline    app-render / page-render / route-handler / RSC
│  packages/next/src/server/app-render/, route-modules/
├───────────────────────────────────────────────────────┤
│  ⑤ Client Runtime     app-router / segment-cache / Link / Image / Script
│  packages/next/src/client/
└───────────────────────────────────────────────────────┘

这 5 层有非常清晰的"上下游"关系:

  • CLI 层 解析 flag、决定走 dev / build / start 哪条路径;
  • 构建层 把用户的 app/ 目录、依赖、内置 templates 编译出 3 套 bundle(client / server / edge);
  • 服务运行时 接管 HTTP,做路由匹配、缓存命中、调用渲染;
  • 渲染管线 是 Next.js 真正的"心脏",跑 React Server Components、SSR、Streaming,最后吐出 HTML 流 + RSC payload;
  • 客户端运行时 负责接住 RSC payload、做 segment cache、prefetch、hydration、SPA 导航。

这本系列后续 39 讲就是按这 5 层逐个展开。记住这张图,你以后看到任何 Next.js 问题,先问自己一句:这是哪一层的问题?

4. 一个最小 App Router 实验

我们生成一个最小 fixture,亲眼看到 RSC payload。

步骤 1:生成 fixture

AGENTS.md 的约定,使用仓库内的脚手架命令:

pnpm new-test -- --args true lecture-01 e2e

这会在 test/e2e/lecture-01/ 下生成最小 App Router 模板(app/page.tsx + 测试文件)。

步骤 2:直接启动 Next.js 看效果

为了避开 Jest 测试框架的开销,直接用 dist 里的 next 命令跑这个目录:

# 先确保 packages/next 已构建
pnpm --filter=next build

# 在 fixture 目录起 dev server
cd test/e2e/lecture-01
node ../../../packages/next/dist/bin/next dev --port 3000

步骤 3:抓 HTML 与 RSC payload

打开第二个终端:

# 抓首屏 HTML(注意末尾 self.__next_f.push 的脚本块)
curl -s http://localhost:3000/ | head -200

# 抓 RSC payload(同一个 URL,加 _rsc 参数 + RSC header)
curl -s -H 'RSC: 1' 'http://localhost:3000/?_rsc=1' | head -50

你会看到两类完全不同的输出:

  • 第一个返回 HTML + 内嵌的 self.__next_f.push([1, "..."]),这是 SSR 的产物,里面已经包含了 RSC payload 的内联拷贝。
  • 第二个返回 纯 RSC payload(看起来像 0:["$","$L1",null,{...}] 这种行式格式),这是浏览器在 SPA 导航时拉取的格式。

这就是 App Router 与传统 SSR 框架的根本差异:HTML 不再是唯一的"传输介质",RSC payload 才是 Next.js 的"原始数据流"。后续第 17 讲会拆开这个 payload 协议。

5. 重难点

5.1 不要拿 Pages 思维理解 App

  • App Router 里 page.tsx 默认是 Server Component,你 import React Context、用 useState 都会立刻报错。
  • "我以前用 getServerSideProps 拿数据"——在 App Router 里直接 const data = await fetch(...),省略中间层。
  • "我以前用 _app.tsx 包 Provider"——在 App Router 里用 app/layout.tsx,并把 Provider 抽成 'use client' 子组件。

5.2 "服务器组件"不等于"在服务器渲染"

  • Server Component 在 next build / next dev 期间也会运行——但不会被打到 client bundle 里。
  • 一个 'use client' 组件在第一次访问页面时也会在服务器跑一次(SSR),只是后续 hydration 后才在浏览器接管。
  • 这一点是很多 hydration mismatch、window is not defined 报错的根因,第 7 讲展开。

5.3 框架边界 ≠ 业务边界

学 Next.js 时最大的陷阱是把"业务怎么写"和"框架怎么实现"混在一起。本课程会严格区分:

  • 使用层(API 怎么调):只到第 14 讲为止还会大量提;
  • 框架层(源码怎么跑):第 15 讲开始会以源码为主,不再举业务示例。

6. 检验问题

学完本讲,你应当能口头回答:

  1. Next.js 的"五件套架构"是什么?分别落在 packages/next/src/ 的哪些子目录?
  2. 为什么 App Router 默认是 Server Component?这与 React 18 / RSC 提案是什么关系?
  3. pages/app/ 共存时,路由冲突怎么处理?(提示:看 route-matcher-managers/
  4. 抓一个 ?_rsc=1 请求,能不能描述 RSC payload 的第一行格式?
  5. 'use client' 在编译期与运行期分别做了什么?(这一题留到第 7 讲再深入)

7. 延伸阅读

  • 官方文档:docs/01-app/01-getting-started/index.mdx 起的整个 App Router 入门系列。
  • React Server Components 提案:react.dev 上的 Server Components RFC(基础理论)。
  • 仓库 AGENTS.md 顶部「Codebase structure」部分(必读)。

下一讲预告

第 02 讲|Monorepo 结构与发布包关系:我们会把 packages/turbopack/crates/ 全部展开,建立"我要修这个 bug,应该改哪个包"的肌肉记忆。