fix(build): 修复静态路径构建回归#4046
Conversation
- 恢复三个 prefix/slug 路由对 isExport helper 的导入,避免 getStaticProps 在预渲染时触发 ReferenceError - 清理 staticPaths 中失败的进程内 allPages promise,避免一次 Notion 拉取失败后持续复用 rejected promise - 统一 staticPaths 缓存 key 的 pageId 默认值,减少未来直接调用时的缓存分歧 - 先清理 tagOptions 再过滤页面 tagItems,避免保留已过滤标签导致标签页 404 验证: - yarn test --runInBand - yarn type-check - eslint 本次改动文件(0 errors,pages/[prefix]/index.js 保留既有 hook warnings) - yarn build(第一次受 Notion ECONNRESET 影响失败,第二次通过) - yarn export 说明: - 未调整 fetchGlobalAllData 的 deepClone 顺序;当前上游仍是先克隆再清理,不会污染 site_ 缓存 - 未移除 build_static_paths_all_pages 缓存;它服务于 notionnext-org#4033 的跨 worker 构建复用,保留更稳
🔍 PR #4046 审查结论:✅ 核心修复:
|
| 文件 | 修复 |
|---|---|
pages/[prefix]/index.js |
+1行,恢复 isExport 导入 |
pages/[prefix]/[slug]/index.js |
+1行,恢复 isExport 导入 |
pages/[prefix]/[slug]/[...suffix].js |
+1行,恢复 isExport 导入 |
这和之前我们分析出的问题完全吻合,修法也是最直接正确的。
✅ 额外修复 1:rejected promise 缓存泄漏 —— 修得很好
// 修复前:失败后 rejected promise 永远留在 Map 里,后续请求全部失败
inProcessAllPagesPromises.set(cacheKey, promise)
// 修复后:失败时自动清理,下次请求可以重试
promise.catch(() => {
inProcessAllPagesPromises.delete(cacheKey)
})
inProcessAllPagesPromises.set(cacheKey, promise)没有竞态条件问题:
.catch()在 JS 中无论 promise 是否已 rejected 都会触发(微任务机制保证).catch()只做 Map 清理,不影响调用方对原始 promise 的await- 新增了专门的测试用例验证此行为
✅ 额外修复 2:缓存键默认值对齐 —— 修得到位
// 修复前:pageId 为 null 时,staticPaths 用 'default',SiteDataApi 用 BLOG.NOTION_PAGE_ID
const safePageId = String(pageId || 'default').replace(...)
// 修复后:两处统一使用 BLOG.NOTION_PAGE_ID
const safePageId = String(pageId || BLOG.NOTION_PAGE_ID).replace(...)这个 bug 虽然在当前正常调用路径下不会触发(因为参数有默认值),但防御性地修正确是正确的做法。
✅ 额外修复 3:tag 清理顺序 —— 发现了一个真实的用户侧 Bug
// 修复前(错误顺序):
db.allPages = cleanPages(db.allPages, db.tagOptions) // ← 用的是未清理的 tagOptions
db.tagOptions = cleanTagOptions(db?.tagOptions) // ← 清理太晚了
// 修复后(正确顺序):
db.tagOptions = cleanTagOptions(db?.tagOptions) // ← 先清理 tagOptions
db.allPages = cleanPages(db.allPages, db.tagOptions) // ← 再用清理后的 tagOptions 过滤页面这是一个真实影响用户的 bug:旧的顺序会导致页面的 tagItems 保留对已删除标签的引用,用户点击标签后出现 404。这个 bug 和 PR #4033 无关,但被这个修复 PR 顺带发现了,修得很好。
📊 与竞品 PR #4044 的对比
| 对比项 | PR #4044 | PR #4046(88lin) |
|---|---|---|
isExport 修复方式 |
内联 process.env.EXPORT === 'true' |
恢复 import { isExport } |
| rejected promise 泄漏 | ❌ 没修 | ✅ 修了 |
| 缓存键默认值不一致 | ❌ 没修 | ✅ 修了 |
| tag 清理顺序 bug | ❌ 没修 | ✅ 修了 |
| 测试覆盖 | ❌ 没加 | ✅ 新增 1 个测试 |
| 改动量 | +9/-3(3文件) | +39/-2(6文件) |
结论:PR #4046 明显优于 PR #4044。 PR #4044 只做了最小修复(甚至修法不太规范——用内联变量替代共享函数),而 88lin 的 PR #4046 不仅修了崩溃问题,还一并修复了 3 个潜在/实际 bug,且有测试覆盖。
🏁 最终结论
| 评估项 | 结论 |
|---|---|
| 能否解决构建报错? | ✅ 可以,isExport 导入恢复后 ReferenceError 消失 |
| 修复质量 | ✅ 高质量,还额外修了 3 个 bug |
| 是否引入新问题? | ❌ 没有发现 |
| CI 状态 | CodeQL ✅ / Build 🔄 进行中 |
| 建议 | 推荐合并,等 CI 通过后即可合入 |
…4046) - Align staticPaths cache key default pageId with fetchGlobalAllData (BLOG.NOTION_PAGE_ID) - Clear in-process allPages promise entry on rejection so builds can retry - Run cleanTagOptions before cleanPages to avoid stale tagItems -> 404 - Add regression test for rejected then successful getSharedAllPages Implements #4046; relates to #4043. Co-authored-by: Cursor <cursoragent@cursor.com>
|
已在 由于 若需保留 PR 记录为 merged,可由贡献者将分支 rebase 到最新 |
|
Superseded: equivalent changes merged to main in 5700f36 (see PR comment). |
背景
PR #4033 将多个路由中的
getStaticPaths逻辑抽到getStaticPathsBase,用于复用静态路径生成和构建缓存逻辑。合并后,三个 prefix/slug 路由文件的getStaticProps仍然会调用isExport()来决定是否设置 ISRrevalidate,但对应的isExport导入被移除,导致构建预渲染阶段直接失败。相关部署报错见:
典型错误为:
该问题会影响 Vercel、Cloudflare Pages、本地
yarn build/yarn export等构建链路。修复内容
本 PR 在保留 #4033 构建优化方向的基础上,补齐合并后遗漏的构建稳定性修复:
isExporthelper 的导入:pages/[prefix]/index.jspages/[prefix]/[slug]/index.jspages/[prefix]/[slug]/[...suffix].js@/lib/utils/buildMode中的isExport(),避免在多个路由文件中重复内联导出模式判断逻辑。getStaticPathsBase负责统一处理 export / ISR 两种构建路径,减少路由文件里的重复逻辑。getSharedAllPages对失败 promise 的进程内缓存问题:当 Notion 数据拉取失败时清理对应缓存 key,避免后续请求一直复用同一个 rejected promise。staticPaths缓存 key 的pageId默认值,使其与fetchGlobalAllData的默认行为保持一致。tagOptions,再用清理后的标签集合过滤页面tagItems,避免页面保留已被过滤标签导致标签页跳转 404。修复效果
isExport is not defined导致的预渲染崩溃。验证
已在修复分支执行以下验证:
yarn install --frozen-lockfileyarn test __tests__/lib/staticPaths.test.js --runInBandyarn test --runInBand./node_modules/.bin/eslint pages/[prefix]/index.js pages/[prefix]/[slug]/index.js pages/[prefix]/[slug]/[...suffix].js lib/build/staticPaths.js lib/db/SiteDataApi.js __tests__/lib/staticPaths.test.jsyarn type-checkyarn buildyarn export验证结果:
yarn build和yarn export均已通过。ReferenceError: isExport is not defined。