本篇是《手写一个内容型技术博客站》系列第 3 篇。
调试内容站最容易抓狂的不是业务 bug,而是"环境问题伪装成代码问题"。这篇记录三个真实踩过的坑,每一个都曾让我对着"看起来是对的"代码怀疑人生。
一、Turbopack 持久缓存重放旧失败
症状
npm run build 报:
Can't resolve '@tailwindcss/typography' in '<project>/src/app'
CssSyntaxError in globals.css:1:1于是按套路排查:
package.json/package-lock.json/node_modules全部验证无误require.resolve('@tailwindcss/typography')成功- 删除 lock + node_modules 重装
npm ci后报错一字不变
诡异到极点:环境验证全对,build 却还报错。
根因
第一次 build 失败时,Turbopack 把"无法解析模块"的失败结果写进了 .next/build 持久缓存。之后每次 build 命中该陈旧缓存,重放同样的失败——报错栈里的 turbopack:///[turbopack-node]/... 就是缓存重放产物。
rm -rf node_modules 清不掉它,因为缓存根本不在 node_modules 里。
修法与铁律
rm -rf .next && npm run build铁律:环境报"依赖缺失/解析失败"但磁盘全验证无误时,先清 .next 再怀疑配置。 生产构建一律 rm -rf .next && npm run build,不要依赖 Turbopack 增量缓存,否则会重放旧失败。
附带坑:有人删
package-lock.json重装,会让tailwindcss系列版本漂移(^4解析到 3.x 时@plugin语法不受支持)。锁 v4 后重建 lock 前先确认tailwindcss/@tailwindcss/postcss是 4.x。
二、孤儿进程占端口:EADDRINUSE 假回归
症状
"我明明改了源码,重跑 build 后 next start,页面还是旧样式"。或者"dev 端口老是被占,新 server 起不来"。
根因
next start / next dev 的守护链是 npm exec → sh -c 'next' → next-server。只杀了外层 npm/next 父进程,next-server 子进程成了孤儿继续占着端口。新 server 因为 EADDRINUSE 起不来——但你以为它起来了,curl 打的其实是旧进程的旧产物。
排查顺序
# 1. 看端口被谁占
ss -tlnp | grep <port>
# 2. 列出 next 相关进程,看启动时间区分新旧
ps -eo pid,lstart,cmd | grep next-server
# 3. 只杀目标端口进程链(父+子都要)
# 用 ss 确认 owner PID,kill -9 孤儿 server PID
# 杀完二次确认端口已空
ss -tlnp | grep <port>关键点:
- 按端口定位 owner,别无差别
pkill -f "next"——会误杀别的实例,还会匹配到调用链自身(SIGKILL 自己)。 - 起服后看日志确认
Ready in Xms而非 EADDRINUSE,才算真起来。 - 可能有多个 next-server 并存(不同时间起的,如 build 前起的旧 + build 后被挡下的新),用
ps -eo lstart区分,全部杀净。
归因心法:别怀疑源码——先怀疑打到旧进程。 各种"我改对了怎么没生效",一半是这个。
三、CRLF 行尾:frontmatter 未剥离
症状
某篇文章详情页顶部出现:
---
title: xxx
slug: xxx整个 frontmatter 被当成正文渲染出来了,标题、字段名全显示在页面顶部。
根因
processMarkdown 剥离 frontmatter 的正则:
/^---\n[\s\S]*?\n---\n?/只匹配 LF(\n)行尾。如果文章是 Windows 环境下编辑保存成 CRLF(\r\n),正则匹配不到 ---\r\n,frontmatter 整段落进正文。
列表页走 matter()(gray-matter)不受影响,所以是"详情页正常、列表正常、偏偏详情页顶部多一段"的诡异形态。
修法
正则改成兼容 CRLF:
/^---\r?\n[\s\S]*?\r?\n---\r?\n?/波及面:所有直接 readFileSync + 渲染的详情页(categories 详情、tutorials 内容页)都要同步改。列表页不受影响。
症状"正文顶部出现 --- 和字段名"先验文件行尾:
file -b <md> # 看是 CRLF 还是 LF
xxd <md> | head # 查 \r\n 字节别一上来怀疑渲染管线。
四、排查顺序总结
这三类"环境伪装"放一起,给一个通用排查优先级:
- 先确认打到的是不是旧产物(端口/进程/缓存)——一半的"改错"是这个
- 环境验证与代码验证分开做——磁盘、node_modules、版本都对,仍有构建错 → 清缓存
- 内容文件先验"字节层"(行尾/NUL/编码),再怀疑渲染逻辑
环境问题的共同特征:错误日志指向的地方,看着都"对",但就是不过。这时候往"缓存、进程、编码"这三个"看不见"的地方想,往往一击即中。
给普通人的一句话:程序告诉你"这里错了",有时不是这里错了,是它看到了过期或错位的东西。
参考
- Turbopack 缓存:https://nextjs.org/docs/app/guides/upgrading
评论
0评论加载中…