Next.js 源码走读:一次 build、一个请求、热更新,框架内部到底发生了什么
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
如果你改了一行样式,npm run dev页面闪一下就生效了,但next build却要重新跑好几分钟——为什么开发态和构建态差这么多?这是新手读 Next.js 源码时最常见的困惑。本文按一条请求的生命周期走读它的三个核心部分:构建期如何从代码产出.next目录,请求期NextServer如何把一次 HTTP 请求派发到具体路由模块,开发期热更新为什么能做到秒级响应。
构建产物从哪里来
先记住一句话:构建期做的事,就是"发现所有路由 + 决定每个路由怎么打包 + 生成一份份清单文件"。
- 入口在 packages/next/src/build/index.ts 的
build()函数,它按 trace span 组织出清晰的阶段:加载.env→ 加载next.config(PHASE_PRODUCTION_BUILD阶段)→ 路由发现 → 编译 → 静态生成。 - 路由发现由
route-discovery.ts里的discoverRoutes完成,把你的app/和pages/目录扫成一张路由映射表——文件系统即路由,落到代码里就是这一步。 - 编译分两条路:Turbopack(现在默认)和 Webpack(src/build/webpack-config.ts 及
webpack/下的 loaders、plugins),外部依赖通过handle-externals.ts剔除出 bundle。 - 最后写出一堆 manifest(build-manifest、routes-manifest、prerender-manifest 等)放进
.next目录,运行时全靠读它们工作。
构建入口的代码结构能直观看到这种"分阶段 + 可观测"的写法:
const nextBuildSpan = trace('next-build', undefined, { buildMode, version }) await nextBuildSpan.traceAsyncFn(async () => { const { loadedEnvFiles } = nextBuildSpan .traceChild('load-dotenv').traceFn(() => loadEnvConfig(dir, false, Log)) const config = await nextBuildSpan .traceChild('load-next-config').traceAsyncFn(() => loadConfig(PHASE_PRODUCTION_BUILD, dir, {...})) })一个请求的完整走位
结论:请求先进"管道",再匹配路由,最后交给具体的路由模块渲染,全程不直接碰你的页面代码。
- HTTP 入口是 packages/next/src/server/ 下的
NextServer(next.ts 中的类),它继承自base-server.ts的基类,负责接收请求、解析 URL。 - 接着依次是:执行中间件/Proxy → 按路由规则匹配(静态路由优先于动态路由)→ 命中
route-modules/下对应类型的模块:app-page、app-route、pages、pages-api,每种路由一个"模块",互不干扰。 - 模块内部才做组件加载(
load-components.ts)和渲染(render.tsx),支持 SSR、SSG 两种产物形态;能直接读磁盘上的预渲染 HTML 的就读,省掉整个 React 渲染。 - 响应缓存和 revalidate 机制也挂在这一层:命中响应缓存的请求根本不会触发渲染。
热重载为什么这么快
回到开头的困惑,答案就藏在这里:开发服务器是"按需编译",只编译你当前访问的页面,而 build 是全量编译。
- 开发入口是 packages/next/src/server/dev/next-dev-server.ts,它包在
NextServer外面,多了一层开发态逻辑。 - 文件变更由
hot-reloader-webpack.ts/hot-reloader-turbopack.ts监听(对应你选择的 bundler),改动后通过hot-middleware.ts推 HMR 消息到浏览器,未失效的模块直接复用缓存。 on-demand-entry-handler.ts负责把"这次编译哪些入口"精确到当前请求的页面——所以开发时改哪页编哪页,而next build会把所有页面都预编译一遍,这就是两者速度差异的根本原因。- 开发态还额外跑类型检查、source map 还原、浏览器日志转发(
browser-logs/),这些在生产态全部不存在。
三条可以带走的设计经验
- 构建期和运行期之间,用 manifest 文件做契约:build 写完、server 只读,两边解耦,任何一边单独演进都不破坏另一边。
- 路由匹配和渲染分开:先做轻量的规则匹配,再委派给对应路由模块,新增一种路由类型(比如 API、Route Handler)不用动主干管道。
- 给每个阶段挂 trace span:Next.js 的构建代码本身就是性能剖析数据,"可观测性"不是事后加的,是写代码时的默认动作。
顺着packages/next/src/server/route-modules/app-page/看一个 App Router 页面的完整渲染链路,是理解整个框架最高效的下一站。
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考