让错误浮现
构建悬挂了。
不是偶尔卡顿,而是彻底停止。进度条不动,终端无响应,只能强制退出。这种问题最令人沮丧,因为它不给你任何线索。
问题不在 Next.js,也不在 TypeScript。问题在于我们选择了掩盖。
掩盖的诱惑
打开 next.config.js,看到这行配置:
typescript: {
ignoreBuildErrors: true
}
再看 tsconfig.json:
{
"compilerOptions": {
"strict": false,
"skipLibCheck": true
}
}
这两个配置共同完成了一件事:让类型错误消失。不是修复,是隐藏。
ignoreBuildErrors: true 让构建继续,但不代表问题不存在。那些被忽略的错误会在运行时爆发,或者在构建过程的某个深层环节制造混乱。
strict: false 关闭了 TypeScript 的核心保护机制。它不会让你的代码运行得更快,只会让问题变得更隐蔽。
为什么会这样配置?通常是因为:
- 遗留代码太多,类型错误修不完
- 赶项目进度,先让它能跑起来
- 不理解严格模式的价值,觉得它碍事
但掩盖问题从来不会让问题消失。它们只是转移到更难发现的地方。
错误的本质
错误不是敌人。错误是信号。
当 TypeScript 报告类型错误时,它在说:"这里的逻辑不够清晰,可能会出问题。"当构建系统悬挂时,它在说:"有些东西不对劲,我无法处理。"
如果我们选择忽略这些信号,系统就会进入一种混乱状态:
- 类型系统无法提供准确的推导
- 构建工具不知道如何处理不明确的代码
- 运行时可能遇到意外的类型冲突
这不是工具的问题,是我们选择掩盖的结果。
参考 fast-type-checking 了解类型检查的性能优化方法,参考 extending-typescript-config 了解如何合理配置 TypeScript。
让错误浮现
修复的方法很简单:停止掩盖。
首先修改 next.config.js,移除或改为:
typescript: {
ignoreBuildErrors: false
}
然后修改 tsconfig.json,启用严格模式:
{
"compilerOptions": {
"strict": true
}
}
接下来运行类型检查:
npx tsc --noEmit
这个命令会暴露所有被掩盖的类型错误。可能有几十个,甚至几百个。这很正常。这些错误本来就在那里,只是被隐藏了。
现在你面对的是真实的问题清单。逐个修复它们。可能需要几个小时,甚至几天。但每修复一个错误,代码就变得更清晰、更安全。
当所有错误都解决后,再次运行构建:
npm run build
构建不会再悬挂。因为那些导致混乱的隐患已经被清除。
修复类型错误能解决大部分构建悬挂问题,但不是全部。如果启用严格模式并修复所有类型错误后,构建仍然悬挂,可能需要检查:
- 依赖版本冲突(
npm ls查看依赖树) - 循环依赖(使用
madge等工具检测) - 内存不足(尝试增加 Node.js 内存限制:
NODE_OPTIONS=--max-old-space-size=4096) - 构建工具配置问题(检查 Webpack、Babel 等配置)
但首要任务始终是让类型系统健康。只有在类型清晰的基础上,才能有效诊断其他问题。
如果错误太多难以一次性处理,可以采用渐进式策略:
- 先启用
ignoreBuildErrors: false,但保持strict: false - 修复所有当前可见的错误
- 逐步启用严格模式的单个选项(如
noImplicitAny、strictNullChecks) - 每启用一个选项,修复相应的错误
- 最终完全启用
strict: true
这种方法更温和,但需要更多耐心。关键是不要停下来,不要回退。
质量不是选项
严格类型检查不是额外的工作,而是基本的质量保证。它不会拖慢开发速度,反而会减少调试时间和线上问题。
当你选择 ignoreBuildErrors: true 时,你在说:"我不在乎这些错误。"但系统会在乎。它会用更隐蔽、更难修复的方式提醒你。
当你选择 strict: false 时,你在说:"我不需要类型安全。"但类型安全不是可有可无的装饰,它是代码可维护性的基础。
技术债不是欠了多少行代码要重写,而是欠了多少问题要解决。每一个被掩盖的错误都是一笔债。利息会复利增长。
让错误浮现,不是为了制造麻烦,而是为了面对真相。只有看到问题,才能解决问题。
最后
构建悬挂问题修复了。不是因为我们找到了某个神奇的配置,而是因为我们停止了掩盖。
那些被忽略的类型错误终于暴露出来,被逐个修复。代码变得更清晰,构建变得更稳定。
这不是一个关于技术的故事,而是关于选择的故事。每一次选择掩盖问题,我们都在为未来的混乱买单。每一次选择面对问题,我们都在为长期的质量投资。
质量不是一次性的行动,而是持续的选择。
选择让错误浮现,而不是让它们沉没。
Related Posts
Articles you might also find interesting
继承基础配置
配置不需要重复书写。继承机制让每个层次只表达自己的差异。
Purikura的页面系统
通过五层分层继承复用架构,实现零代码修改的页面生成系统。从类型定义到页面渲染,每一层专注单一职责,实现真正的数据驱动开发。
动态元数据生成:让机器读懂你的页面
generateMetadata 不只是填写表单。它决定了搜索引擎、社交平台、AI 系统如何理解和呈现你的内容。
tsc --noEmit:即时类型反馈
类型错误不应该等到构建时才发现。最快的反馈来自最简单的命令。
Google Fonts 官方集成
Next.js 提供了 next/font 模块,让字体加载变得简单且性能优化。Google Fonts 是最直接的商用免费字体选择。
全局安装 TypeScript
当 tsc 命令未找到时,你缺少的不是命令,而是编译器本身。
next-intl localePrefix:默认语言不显示前缀
理解 next-intl 中 localePrefix 配置的设计哲学,以及为什么默认语言不应在 URL 中显现。
next-intl 的服务端与客户端协同机制
理解 next-intl 如何在 Next.js App Router 中协调服务端渲染和客户端交互,以及为什么需要显式设置 locale。
减少 Next.js 启动时的工作量
开发服务器启动缓慢不是偶然。它在做的事太多了。
运行时类型契约
TypeScript 的类型在编译后消失。真正的安全需要在边界处延续类型契约。
用静态导出控制视口
Next.js 中的视口配置通过静态导出模式定义页面初始状态,理解其背后的设计约束能够更好地控制用户体验边界。