基于源码深度解析
2026-03-09 | 技术深度解读
第一部分:基础架构
第二部分:演进历史
第三部分:核心类解析
第四部分:核心函数
Next.js 是 React 生态中最流行的全栈框架,提供了强大的文件系统路由。
核心特性
路由系统目标
Next.js 路由系统采用分层架构,从请求接收到页面渲染形成完整链路。
| 层级 | 职责 | 核心类 |
|---|---|---|
| Server | 请求入口、协调各组件 | Server |
| Matcher | 路由匹配、参数提取 | RouteMatcherManager |
| Provider | 路由定义加载 | RouteMatcherProvider |
| Normalizer | URL 规范化处理 | PathnameNormalizer |
| I18N | 国际化路由 | I18NProvider |
从请求到响应的完整处理流程:
handleRequest(req, res)
↓
attachRequestMeta(req, parsedUrl) // 挂载元数据
↓
handleRSCRequest(req, res) // RSC 请求处理
↓
handleNextDataRequest(req, res) // Data 请求处理
↓
matchers.match(pathname) // 路由匹配
↓
handleRewrites(req, parsedUrl) // Rewrite 处理
↓
renderPage(req, res, match) // 页面渲染
Next.js 支持四种核心路由类型,覆盖所有使用场景:
| 类型 | 路径 | 说明 |
|---|---|---|
| PAGES | pages/*.tsx | 传统页面路由 |
| PAGES_API | pages/api/*.ts | API 路由 |
| APP_PAGE | app/*/page.tsx | App Router 页面 |
| APP_ROUTE | app/*/route.ts | App Router API |
| IMAGE | _next/image | 图片优化 |
Pages Router 时代
App Router 时代
| 特性 | Pages Router | App Router |
|---|---|---|
| 路由定义 | 文件名 | 文件夹 + page.tsx |
| 布局 | _app.js | layout.tsx |
| 数据获取 | getServerSideProps | async 组件 |
| 加载状态 | 手动实现 | loading.tsx |
| 错误处理 | _error.js | error.tsx |
| 流式渲染 | 不支持 | 原生支持 |
Next.js 路由系统的核心类型定义:
// 路由类型
RouteKind → PAGES | PAGES_API | APP_PAGE | APP_ROUTE | IMAGE
// 路由定义
RouteDefinition → kind, bundlePath, filename, page, pathname
// 路由匹配
RouteMatch → definition, params
// 请求元数据
RequestMeta → 50+ 字段,包含请求上下文
// 路由正则
RouteRegex → re, groups
定义所有路由类型的枚举常量:
export const enum RouteKind {
/** pages/ 下的 React 页面 */
PAGES = 'PAGES',
/** pages/api/ 下的 API 路由 */
PAGES_API = 'PAGES_API',
/** app/ 下的页面 page.tsx */
APP_PAGE = 'APP_PAGE',
/** app/ 下的路由 route.ts */
APP_ROUTE = 'APP_ROUTE',
/** next/image 生成的图片 */
IMAGE = 'IMAGE',
}
描述路由定义的元信息:
export interface RouteDefinition {
/** 路由类型 */
readonly kind: K
/** 打包路径 */
readonly bundlePath: string
/** 文件名 */
readonly filename: string
/** 页面路径(含内部修饰符) */
readonly page: string
/** 路径名(含动态占位符) */
readonly pathname: string
}
表示路由匹配结果,包含动态参数:
export interface RouteMatch {
/** 匹配的路由定义 */
readonly definition: D
/** 动态路由参数(从 URL 解析) */
readonly params: Params | undefined
}
// Params 类型
type Params = Record<string, string | string[]>
// 示例: /posts/[slug] 匹配 /posts/hello
// params = { slug: 'hello' }
请求元数据接口,包含50+ 字段:
export interface RequestMeta {
// URL 相关
initURL?: string
initQuery?: ParsedUrlQuery
resolvedPathname?: string
rewrittenPathname?: string
// 国际化
locale?: string
defaultLocale?: string
isLocaleDomain?: boolean
// RSC 相关
isRSCRequest?: true
isPrefetchRSCRequest?: true
segmentPrefetchRSCRequest?: string
// 路由匹配
match?: RouteMatch
invokePath?: string
invokeStatus?: number
// 缓存
incrementalCache?: IncrementalCache
// ... 更多字段
}
路由匹配管理器接口定义:
export interface RouteMatcherManager {
/** 等待匹配器加载完成 */
waitTillReady(): Promise<void>
/** 添加匹配器提供者 */
push(provider: RouteMatcherProvider): void
/** 重新加载匹配器 */
reload(): Promise<void>
/** 测试是否匹配 */
test(pathname: string, options: MatchOptions): Promise<boolean>
/** 返回第一个匹配 */
match(pathname: string, options: MatchOptions): Promise<RouteMatch | null>
/** 返回所有匹配(生成器) */
matchAll(pathname: string, options: MatchOptions): AsyncGenerator<RouteMatch>
}
Next.js 服务器的抽象基类:
export default abstract class Server<ServerOptions, ServerRequest, ServerResponse> {
// 核心属性
public readonly hostname?: string
public readonly port?: number
protected readonly dir: string
protected readonly nextConfig: NextConfigRuntime
protected readonly dev: boolean
// 路由匹配器
public readonly matchers: RouteMatcherManager
// 国际化
protected readonly i18nProvider?: I18NProvider
// 核心方法
public async handleRequest(req, res, parsedUrl)
protected abstract findPageComponents(params)
protected abstract renderHTML(req, res, pathname, query)
}
配置属性
运行时属性
国际化路由提供者:
export class I18NProvider {
constructor(public readonly config: Readonly<I18NConfig>) {}
/** 从域名检测语言 */
public detectDomainLocale(hostname?: string): DomainLocale | undefined
/** 从请求获取语言分析结果 */
public fromRequest(req: NextIncomingMessage, pathname: string): LocaleAnalysisResult
/** 分析路径名中的语言 */
public analyze(pathname: string, options?: LocaleAnalysisOptions): LocaleAnalysisResult
}
// 示例: /en/posts → { detectedLocale: 'en', pathname: '/posts' }
语言分析结果接口:
export interface LocaleAnalysisResult {
/** 去除语言前缀的路径名 */
pathname: string
/** 检测到的语言 */
detectedLocale?: string
/** 是否从默认语言推断 */
inferredFromDefault: boolean
}
// 分析示例
analyze('/zh-CN/posts')
// → { pathname: '/posts', detectedLocale: 'zh-CN', inferredFromDefault: false }
analyze('/posts')
// → { pathname: '/posts', detectedLocale: undefined, inferredFromDefault: true }
路由正则表达式生成结果:
export interface RouteRegex {
/** 捕获组信息 */
groups: { [groupName: string]: Group }
/** 编译后的正则表达式 */
re: RegExp
}
// 示例: /posts/[slug]
// re: /^\/posts\/([^/]+?)(?:/)?$/
// groups: { slug: { pos: 1, repeat: false, optional: false } }
正则捕获组元信息:
export interface Group {
/** 在正则中的位置(从 1 开始) */
pos: number
/** 是否为重复参数 [...slug] */
repeat: boolean
/** 是否为可选参数 [[...slug]] */
optional: boolean
}
// 路由参数类型映射:
// [slug] → { repeat: false, optional: false }
// [...slug] → { repeat: true, optional: false }
// [[...slug]] → { repeat: true, optional: true }
将路由路径编译为正则表达式:
export function getRouteRegex(
normalizedRoute: string,
options?: GetRouteRegexOptions
): RouteRegex {
const { parameterizedRoute, groups } = getParametrizedRoute(
normalizedRoute,
includeSuffix,
includePrefix
)
let re = parameterizedRoute
if (!excludeOptionalTrailingSlash) {
re += '(?:/)?' // 可选尾部斜杠
}
return {
re: new RegExp(`^${re}$`),
groups: groups,
}
}
// /posts/[slug] → /^\/posts\/([^/]+?)(?:/)?$/
创建路由匹配函数:
export function getRouteMatcher({
re,
groups,
}: RouteMatcherOptions): RouteMatchFn {
return (pathname: string) => {
const routeMatch = re.exec(pathname)
if (!routeMatch) return false
const params: Params = {}
for (const [key, group] of Object.entries(groups)) {
const match = routeMatch[group.pos]
if (match !== undefined) {
if (group.repeat) {
// [...slug] → 数组
params[key] = match.split('/').map(decode)
} else {
// [slug] → 字符串
params[key] = decode(match)
}
}
}
return params
}
}
检测路由是否为动态路由:
// 动态路由正则
const TEST_ROUTE = /\/[^/]*\[[^/]+\][^/]*(?=\/|$)/
const TEST_STRICT_ROUTE = /\/\[[^/]+\](?=\/|$)/
export function isDynamicRoute(
route: string,
strict: boolean = true
): boolean {
// 处理拦截路由
if (isInterceptionRouteAppPath(route)) {
route = extractInterceptionRouteInformation(route).interceptedRoute
}
if (strict) {
return TEST_STRICT_ROUTE.test(route)
}
return TEST_ROUTE.test(route)
}
// isDynamicRoute('/posts/[slug]') → true
// isDynamicRoute('/posts') → false
服务器请求处理入口:
public async handleRequest(req, res, parsedUrl?): Promise<void> {
await this.prepare()
// OTEL 追踪
return tracer.trace(BaseServerSpan.handleRequest, async (span) => {
// 等待匹配器就绪
await this.matchers.waitTillReady()
// Cookie 合并补丁
patchSetHeaderWithCookieSupport(req, res)
// URL 规范化(处理重复斜杠)
if (urlNoQuery?.match(/(\\|\/\/)/)) {
const cleanUrl = normalizeRepeatedSlashes(req.url!)
res.redirect(cleanUrl, 308)
return
}
// 解析 URL
if (!parsedUrl) parsedUrl = parseUrl(req.url)
// 设置转发头
req.headers['x-forwarded-host'] ??= req.headers['host']
// 挂载请求元数据
this.attachRequestMeta(req, parsedUrl)
// 处理请求...
})
}
执行路由匹配:
// RouteMatcherManager.match 实现
async match(pathname: string, options: MatchOptions): Promise<RouteMatch | null> {
// 遍历所有匹配器
for await (const match of this.matchAll(pathname, options)) {
return match // 返回第一个匹配
}
return null
}
// 在 Server 中使用
const match = await this.matchers.match(srcPathname, {
i18n: localeAnalysisResult,
})
if (match) {
// 更新路径名和参数
srcPathname = match.definition.pathname
if (match.params) {
pageIsDynamic = true
params = match.params
}
}
处理React Server Components 请求:
private handleRSCRequest = (req, res, parsedUrl) => {
// 段预取 RSC 请求
if (this.normalizers.segmentPrefetchRSC?.match(parsedUrl.pathname)) {
const { originalPathname, segmentPath } =
this.normalizers.segmentPrefetchRSC.extract(parsedUrl.pathname)
parsedUrl.pathname = originalPathname
req.headers[RSC_HEADER] = '1'
req.headers[NEXT_ROUTER_PREFETCH_HEADER] = '1'
req.headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER] = segmentPath
addRequestMeta(req, 'isRSCRequest', true)
addRequestMeta(req, 'isPrefetchRSCRequest', true)
}
// 普通 RSC 请求
else if (this.normalizers.rsc?.match(parsedUrl.pathname)) {
parsedUrl.pathname = this.normalizers.rsc.normalize(parsedUrl.pathname)
req.headers[RSC_HEADER] = '1'
addRequestMeta(req, 'isRSCRequest', true)
}
return false // 继续处理
}
处理 _next/data 请求:
private handleNextDataRequest = async (req, res, parsedUrl) => {
const params = matchNextDataPathname(parsedUrl.pathname)
// 验证 buildId
if (params.path[0] !== this.buildId) {
await this.render404(req, res, parsedUrl)
return true
}
// 移除 buildId,重建页面路径
params.path.shift()
let pathname = `/${params.path.join('/')}`
pathname = getRouteFromAssetPath(pathname, '.json')
// 国际化处理
if (this.i18nProvider) {
const localePathResult = this.i18nProvider.analyze(pathname)
if (localePathResult.detectedLocale) {
pathname = localePathResult.pathname
}
}
parsedUrl.pathname = pathname
addRequestMeta(req, 'isNextDataReq', true)
return false // 继续处理
}
参数解析的优先级顺序:
// 1. 从 RouteMatcher 获取
if (match?.params) {
params = match.params
}
// 2. 从 URL 直接解析
if (!hasValidParams && !isDynamicRoute(normalizedUrlPath)) {
const matcherParams = utils.dynamicRouteMatcher?.(normalizedUrlPath)
}
// 3. 从 x-now-route-matches 头获取
const routeMatchesHeader = req.headers['x-now-route-matches']
if (routeMatchesHeader && isDynamicRoute(matchedPath)) {
const routeMatches = utils.getParamsFromRouteMatches(routeMatchesHeader)
}
// 4. 从 Query 参数获取
if (!hasValidParams) {
paramsResult = utils.normalizeDynamicRouteParams(rewrittenQueryParams, true)
}
// 5. 使用默认匹配
if (utils.defaultRouteMatches && !hasValidParams) {
params = utils.defaultRouteMatches
}
动态参数的解码与清理:
const decode = (param: string) => {
try {
return decodeURIComponent(param)
} catch {
throw new DecodeError('failed to decode param')
}
}
// 参数清理包装器
export function safeRouteMatcher(rawMatcher: RouteMatchFn): RouteMatchFn {
return (pathname: string) => {
const params = rawMatcher(pathname)
if (!params) return false
// 清理无效参数
for (const key of Object.keys(params)) {
if (params[key] === undefined || params[key] === null) {
delete params[key]
}
}
return params
}
}
URL 重写的处理逻辑:
// 保存原始查询参数
const originQueryParams = { ...parsedUrl.query }
// 执行重写
const { rewriteParams, rewrittenParsedUrl } = utils.handleRewrites(
req,
parsedUrl
)
const didRewrite = pathnameBeforeRewrite !== rewrittenParsedUrl.pathname
if (didRewrite && rewrittenParsedUrl.pathname) {
addRequestMeta(req, 'rewrittenPathname', rewrittenParsedUrl.pathname)
}
// 处理路由参数
for (const [key, value] of Object.entries(parsedUrl.query)) {
const normalizedKey = normalizeNextQueryParam(key)
if (!normalizedKey) continue
delete parsedUrl.query[key] // 移除前缀键
routeParamKeys.add(normalizedKey)
// 解码参数值
rewrittenQueryParams[normalizedKey] = decodeQueryPathParameter(value)
}
RouteMatcherProvider 使用工厂模式创建匹配器:
// getRouteMatchers 工厂方法
protected getRouteMatchers(): RouteMatcherManager {
const matchers: RouteMatcherManager = new DefaultRouteMatcherManager()
// Pages 路由
matchers.push(
new PagesRouteMatcherProvider(this.distDir, manifestLoader, this.i18nProvider)
)
// Pages API 路由
matchers.push(
new PagesAPIRouteMatcherProvider(this.distDir, manifestLoader, this.i18nProvider)
)
// App Router(如果启用)
if (this.enabledDirectories.app) {
matchers.push(new AppPageRouteMatcherProvider(this.distDir, manifestLoader))
matchers.push(new AppRouteRouteMatcherProvider(this.distDir, manifestLoader))
}
return matchers
}
不同路由类型使用不同的匹配策略:
| 路由类型 | 匹配策略 | Provider |
|---|---|---|
| PAGES | 文件名匹配 + i18n | PagesRouteMatcherProvider |
| PAGES_API | 文件名匹配 + i18n | PagesAPIRouteMatcherProvider |
| APP_PAGE | 文件夹结构匹配 | AppPageRouteMatcherProvider |
| APP_ROUTE | API 路由匹配 | AppRouteRouteMatcherProvider |
每种策略实现统一的 RouteMatcherProvider 接口。
请求处理形成处理链:
// 请求处理链
handleRequest(req, res, parsedUrl)
↓
handleRSCRequest() // RSC 请求?→ 处理/跳过
↓
handleNextDataRequest() // Data 请求?→ 处理/跳过
↓
handleNextImageRequest() // 图片请求?→ 处理/跳过
↓
matchers.match() // 路由匹配
↓
handleRewrites() // Rewrite 处理
↓
renderHTML() // 页面渲染
// 每个处理器返回 true 表示已处理,false 表示继续
路由匹配器的热重载机制:
// Server 构造函数中
void this.matchers.reload() // 启动加载
// 开发模式下监听文件变化
protected reloadMatchers() {
return this.matchers.reload()
}
// RouteMatcherManager 接口
interface RouteMatcherManager {
reload(): Promise<void>
waitTillReady(): Promise<void>
}
// 在 handleRequest 中等待就绪
await this.matchers.waitTillReady()
┌─────────────────────────────────────────────────────────────┐
│ Server │
│ - matchers: RouteMatcherManager │
│ - i18nProvider: I18NProvider │
│ + handleRequest(req, res) │
└─────────────────────┬───────────────────────────────────────┘
│ uses
┌───────────┴───────────┐
▼ ▼
┌─────────────────┐ ┌──────────────────────┐
│RouteMatcher │ │ I18NProvider │
│Manager │ │ - locales │
│ - providers[] │ │ - domains │
│ + match() │ │ + analyze() │
│ + matchAll() │ │ + detectDomainLocale()│
└────────┬────────┘ └──────────────────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│RouteMatcher │ │ RouteDefinition │
│Provider │ │ - kind │
│ + provide() │ │ - pathname │
└────────┬────────┘ │ - page │
│ └─────────────────┘
▼
┌─────────────────┐ ┌─────────────────┐
│ RouteMatch │ │ RouteRegex │
│ - definition │ │ - re │
│ - params │ │ - groups │
└─────────────────┘ └─────────────────┘
HTTP Request
│
▼
┌─────────────────┐
│ handleRequest() │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐
│attachRequestMeta│───→│ RequestMeta │
└────────┬────────┘ │ - initURL │
│ │ - locale │
▼ │ - isRSCRequest │
┌─────────────────┐ └──────────────────┘
│handleRSCRequest │
│ (RSC/预取) │
└────────┬────────┘
│
▼
┌─────────────────┐
│handleNextData │
│Request │
└────────┬────────┘
│
▼
┌─────────────────┐
│ matchers.match()│
└────────┬────────┘
│
▼
┌─────────────────┐
│ handleRewrites │
└────────┬────────┘
│
▼
┌─────────────────┐
│ renderHTML │
└─────────────────┘
matchers.match(pathname, options)
│
▼
┌─────────────────────────┐
│ 遍历所有 Provider │
│ - PagesRouteMatcher │
│ - PagesAPIRouteMatcher │
│ - AppPageRouteMatcher │
│ - AppRouteRouteMatcher │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ 每个 Provider 返回 │
│ Matcher 数组 │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ 遍历 Matcher 执行匹配 │
│ matcher.match(pathname) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ 返回第一个成功的 │
│ RouteMatch │
│ - definition │
│ - params │
└─────────────────────────┘
// 1. 从域名检测语言
const domainLocale = this.i18nProvider?.detectDomainLocale(hostname)
const defaultLocale = domainLocale?.defaultLocale || config.defaultLocale
// 2. 分析 URL 路径
const localeAnalysisResult = this.i18nProvider.analyze(matchedPath, {
defaultLocale,
})
// 3. 更新请求元数据
if (localeAnalysisResult.detectedLocale) {
addRequestMeta(req, 'locale', localeAnalysisResult.detectedLocale)
}
if (localeAnalysisResult.inferredFromDefault) {
addRequestMeta(req, 'localeInferredFromDefault', true)
}
// 4. 移除路径中的语言前缀
if (localeAnalysisResult) {
matchedPath = localeAnalysisResult.pathname
}
基于文件夹结构的路由定义:
app/
├── layout.tsx # 根布局
├── page.tsx # 首页 /
├── loading.tsx # 加载状态
├── error.tsx # 错误处理
├── not-found.tsx # 404 页面
├── (group)/ # 路由组(不影响 URL)
│ ├── layout.tsx
│ └── dashboard/
│ └── page.tsx # /dashboard
├── posts/
│ ├── layout.tsx
│ ├── page.tsx # /posts
│ └── [slug]/
│ └── page.tsx # /posts/[slug]
└── api/
└── route.ts # /api
匹配优化
构建优化
关键优化:避免在请求时解析路由,所有正则在构建时预编译。
开发模式下的热更新机制:
// Server 构造函数
void this.matchers.reload() // 异步加载,不阻塞
// 请求时等待就绪
await this.matchers.waitTillReady()
// 文件变化时重新加载
protected reloadMatchers() {
return this.matchers.reload()
}
// RouteMatcherManager 实现
class DefaultRouteMatcherManager {
private ready: Promise<void>
reload(): Promise<void> {
this.ready = this.loadAllMatchers()
return this.ready
}
waitTillReady(): Promise<void> {
return this.ready
}
}
多层次的缓存策略:
| 缓存层 | 内容 | 生命周期 |
|---|---|---|
| 路由正则 | 编译后的 RegExp | 永久 |
| Manifest | 路由定义 | 构建时 |
| 匹配结果 | RouteMatch | 请求级 |
| 增量缓存 | ISR 页面 | 可配置 |
| HMR 缓存 | 组件模块 | 开发时 |
结构设计
性能优化
避免这些路由设计问题:
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 过度嵌套 | 性能下降 | 使用路由组扁平化 |
| 全部动态路由 | 无法预渲染 | 混合静态/动态 |
| 忽略 i18n | SEO 差 | 规划语言路径 |
| 中间件滥用 | 响应延迟 | 精简中间件逻辑 |
| 大型 layout | 加载慢 | 拆分为小组件 |
Next.js 中间件的最佳实践:
// middleware.ts
export function middleware(request: NextRequest) {
// 1. 只匹配需要的路由
if (!request.nextUrl.pathname.startsWith('/api/')) {
return
}
// 2. 使用 matcher 配置
export const config = {
matcher: ['/api/:path*', '/dashboard/:path*'],
}
// 3. 避免阻塞操作
const response = NextResponse.next()
// 4. 设置响应头传递数据
response.headers.set('x-custom-header', 'value')
return response
}
// 5. 边缘运行时
export const runtime = 'edge'
开发调试
// 启用调试日志
NEXT_PRIVATE_DEBUG=true npm run dev
// 查看路由匹配
console.log('Match:', match)
console.log('Params:', params)
// 检查请求元数据
console.log('RequestMeta:',
getRequestMeta(req))
构建分析
# 分析构建产物
npx @next/bundle-analyzer
# 查看路由 manifest
cat .next/routes-manifest.json
# 检查类型
npx tsc --noEmit
| 特性 | Next.js | Remix | Nuxt |
|---|---|---|---|
| 路由模式 | 文件系统 | 文件系统 | 文件系统 |
| 数据加载 | async 组件 | loader | useFetch |
| SSR | ✅ | ✅ | ✅ |
| RSC | ✅ 原生 | ❌ | ❌ |
| 流式渲染 | ✅ | ✅ | ✅ |
| 边缘部署 | ✅ | ✅ | ✅ |
Next.js 优势:RSC 原生支持、Vercel 深度集成、生态最完善。
短期(2026)
长期
App Router 与 RSC 的深度集成:
// RSC 请求识别
if (req.headers[RSC_HEADER] === '1') {
addRequestMeta(req, 'isRSCRequest', true)
}
// 预取请求
if (req.headers[NEXT_ROUTER_PREFETCH_HEADER] === '1') {
addRequestMeta(req, 'isPrefetchRSCRequest', true)
}
// 段预取
const segmentPath = req.headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER]
if (segmentPath) {
addRequestMeta(req, 'segmentPrefetchRSCRequest', segmentPath)
}
// PPR 恢复
if (req.headers[NEXT_RESUME_HEADER] === '1') {
const postponed = await readBodyWithSizeLimit(req.body)
addRequestMeta(req, 'postponed', postponed)
}
核心要点回顾:
扩展阅读: