减少 Next.js 启动时的工作量

2 min read
Zekari
Next.js性能优化开发体验工作流

启动时发生了什么

当你运行 npm run dev,Next.js 不只是启动一个服务器。

它扫描所有页面文件,解析路由结构,加载配置,初始化编译器,建立文件监听,预编译初始页面。

这些都在你看到"Ready"之前完成。

每一项工作都需要时间。项目越大,页面越多,依赖越复杂,时间越长。

💡 Click the maximize icon to view in fullscreen

开发服务器试图提前准备一切。这是它的设计:让你访问任何页面时都能立即看到结果。

但代价是启动时的等待。

为什么要做这些

Next.js 的核心价值是"开箱即用"。

它不要求你配置 webpack,不需要你手动设置路由,不强制你理解构建流程。你创建文件,它就工作。

这种便利来自大量的自动化。自动化需要分析:扫描文件结构、推断路由规则、确定编译选项。

分析需要时间。

同时,Next.js 试图在开发时模拟生产环境。这意味着它要做很多生产构建才做的事:代码分割、依赖优化、资源处理。

这保证了开发和生产的一致性。但也意味着开发服务器承担了更重的负担。

便利和性能,这是权衡。

Vite 的启动速度远快于 Next.js。它的策略是"按需编译"。

启动时,Vite 几乎什么都不做。它只启动一个服务器。当你访问页面,它才编译那个页面需要的模块。

这种惰性加载让启动极快。但第一次访问页面时会稍慢,因为编译此时才发生。

Next.js 选择了相反的策略:提前编译。它假设你会访问某些页面(通常是首页),所以预先准备好。

两种策略各有代价。Vite 牺牲首次访问速度换取启动速度,Next.js 牺牲启动速度换取访问速度。

参考 lazy-initialization-pattern 了解延迟初始化的哲学。

缓慢的根源

Next.js 启动缓慢的本质不是性能问题,是范围问题

它在启动时做了太多事。

一个典型的 Next.js 项目可能有几十个页面。但你开发时,通常只关注其中一两个。

服务器不知道你关注哪个。所以它准备所有页面。

这是通用性的代价。为了服务所有可能的场景,它必须假设最坏情况:你可能访问任何页面。

同样的逻辑适用于依赖。你的项目可能有数百个 npm 包,但某个页面可能只用其中几个。

服务器在启动时解析所有依赖关系,因为它不知道你会访问哪个页面。

通用性要求全局视野。全局视野需要全局扫描。

这是启动缓慢的根源。

减少工作量

如果缓慢来自"做太多",解决方案是"做更少"。

限制扫描范围。

如果你只在开发一个功能,你可以暂时忽略其他页面。Next.js 没有内置选项来限制扫描哪些页面,但你可以临时移动不相关的页面到项目外。

这减少了路由分析和预编译的负担。

禁用不必要的功能。

TypeScript 类型检查、ESLint、静态分析——这些都可以在启动时禁用。

next.config.js 中:

module.exports = {
  typescript: {
    ignoreBuildErrors: true, // 开发时跳过类型检查
  },
  eslint: {
    ignoreDuringBuilds: true, // 开发时跳过 ESLint
  },
}

类型检查和代码规范很重要,但不需要在启动时发生。你可以在单独的进程中运行它们。参考 fast-type-checking 了解如何独立运行类型检查。

减少依赖。

每个依赖都是启动时的潜在负担。定期检查 package.json,移除不再使用的包。

特别注意大型依赖——UI 库、工具集、框架。如果可能,延迟导入它们(dynamic import),而不是在模块顶层导入。

使用 Turbopack(实验性)。

Next.js 13+ 提供了 Turbopack,一个用 Rust 编写的更快的打包工具:

next dev --turbo

它还在实验阶段,但在大型项目中能显著加快启动速度。

Turbopack 还在积极开发中。某些插件和配置可能不兼容。

在生产环境继续使用标准构建,只在开发时使用 Turbopack。

如果遇到奇怪的错误或不一致的行为,先尝试不用 --turbo 运行,确认问题是否由 Turbopack 引起。

权衡的智慧

优化启动速度不是无代价的。

如果你禁用类型检查,你可能错过类型错误,直到你手动运行检查或提交代码。

如果你限制扫描范围,你可能忘记某个依赖关系,导致其他页面出错。

如果你使用实验性工具,你可能遇到稳定性问题。

每个优化都是权衡。你用其他方面的便利或安全性,换取启动速度。

关键是知道你在交换什么。

在早期开发阶段,你可能需要快速迭代,启动速度优先。禁用所有非必要检查,聚焦功能实现。

在测试和提交阶段,完整性优先。重新启用所有检查,确保代码质量。

不存在"最佳配置"。只有"当前阶段的合适配置"。

💡 Click the maximize icon to view in fullscreen

最后

Next.js 启动缓慢不是缺陷。它是设计选择的自然结果。

选择便利性,就接受启动时的准备工作。选择通用性,就接受全局扫描的成本。

你可以减少这些成本,但不能消除它们。优化是调整权衡的位置,不是消除权衡本身。

理解你的工具在做什么。知道它为什么这样做。然后决定你愿意接受什么,愿意放弃什么。

启动速度只是一个指标。重要的是它对你的工作流影响多大,以及你为优化它愿意付出什么。

工具服务于目的。速度服务于效率。效率服务于创造。

保持这个优先级。

Related Posts

Articles you might also find interesting

Google Fonts 官方集成

2 min read

Next.js 提供了 next/font 模块,让字体加载变得简单且性能优化。Google Fonts 是最直接的商用免费字体选择。

Next.js字体
Read More

动态元数据生成:让机器读懂你的页面

2 min read

generateMetadata 不只是填写表单。它决定了搜索引擎、社交平台、AI 系统如何理解和呈现你的内容。

Next.jsSEO
Read More

继承基础配置

2 min read

配置不需要重复书写。继承机制让每个层次只表达自己的差异。

TypeScript配置管理
Read More

tsc --noEmit:即时类型反馈

2 min read

类型错误不应该等到构建时才发现。最快的反馈来自最简单的命令。

TypeScript类型检查
Read More

Purikura的页面系统

3 min read

通过五层分层继承复用架构,实现零代码修改的页面生成系统。从类型定义到页面渲染,每一层专注单一职责,实现真正的数据驱动开发。

架构设计React
Read More

请求包含 gzip 压缩的任务结果 JSON

2 min read

数据传输的本质是在空间和时间之间做选择,压缩是对带宽的节约,也是对等待的妥协

HTTP性能优化
Read More

引入懒加载模式

1 min read

懒加载不是优化技巧,而是关于时机的选择。何时创建,决定了系统的效率和复杂度。

软件设计性能优化
Read More

让错误浮现

1 min read

Next.js 构建悬挂问题的根源不在工具,而在掩盖。严格类型检查不是负担,而是质量的守护者。

TypeScriptNext.js
Read More

next-intl localePrefix:默认语言不显示前缀

2 min read

理解 next-intl 中 localePrefix 配置的设计哲学,以及为什么默认语言不应在 URL 中显现。

Next.jsi18n
Read More

next-intl 的服务端与客户端协同机制

3 min read

理解 next-intl 如何在 Next.js App Router 中协调服务端渲染和客户端交互,以及为什么需要显式设置 locale。

Next.jsi18n
Read More

用静态导出控制视口

2 min read

Next.js 中的视口配置通过静态导出模式定义页面初始状态,理解其背后的设计约束能够更好地控制用户体验边界。

Next.js视口
Read More

一次一次的代价

2 min read

在表格的每一行里调用一次查询,看起来最直接。但一次一次累积起来,代价会变得巨大。

性能优化系统思维
Read More