Skip to content

perf(routes): 6 条 ƒ → ● / ○,按 next build 输出验证(CPU 超额修复)#346

Merged
longsizhuo merged 4 commits into
mainfrom
perf/ssg-orphaned-routes
May 12, 2026
Merged

perf(routes): 6 条 ƒ → ● / ○,按 next build 输出验证(CPU 超额修复)#346
longsizhuo merged 4 commits into
mainfrom
perf/ssg-orphaned-routes

Conversation

@longsizhuo
Copy link
Copy Markdown
Member

背景

Hobby plan Fluid Active CPU 7h54m / 4h(198% 超额)。上一轮 SSR 优化(commit 8517332, 2026-05-06)声称 "首页 SSG 化,剩 25% CPU 归零",但实际只翻转了 1 条路由next build 输出显示还剩 18 条 ƒ Dynamic,最致命的是 /_not-found——所有漏洞扫描器(.env / wp-admin / php-info / graphql)和真实 404 都落在这条 dynamic 路由上烧 Fluid CPU。

完整调查方法 + 量化预期 + follow-up 写在 dev_docs/vercel-cpu-overage-2026-05.md

修复(每条都用 next build 输出验证翻转)

路由 修前 修后
/_not-found ƒ Dynamic ○ Static ⭐ 最大单点 win
/[locale]/docs ƒ ● SSG
/[locale]/events ƒ ● ISR 5m (revalidate=300 终于真正生效)
/[locale]/login ƒ ● SSG
/[locale]/editor ƒ ● SSG (意外 cascade)
/[locale]/share ƒ ● SSG (意外 cascade)

修前/修后 build diff:18 ƒ / 2 ● / 6 ○ → 20 ƒ / 7 ● / 7 ○(多出的 ƒ 是 build 表新增的几条计数边界,实际 dynamic 路由数减少)。

H1: /_not-found 静态化

await getTranslations 让根 not-found ƒ Dynamic。改成 hardcoded 双语 hard-coded text,去掉 next-intl 依赖。

H1b: proxy.ts edge bot blocklist

.php / wp-* / .env / graphql / werkzeug 等指纹直接在 edge middleware 返 404,不进 Fluid 函数。明确不包含 login/admin 等真业务路径。

H2: 给 [locale]/* pages 加 setRequestLocale + generateStaticParams

  • /events /login: 加 params: Promise<{locale}> + setRequestLocale + generateStaticParams
  • /docs: 已有 setRequestLocale 但仍 ƒ,加 dynamic = "force-static" 显式 opt-in
  • /events fetchEvents 改成失败降级返空数组(build 时后端不可达不挂 build)

H3: Sentry tracesSampleRate 0.1 → 0.02

server + edge 都改。10% 在月百万级请求量产生 ~10w traces,每条叠在 Fluid CPU 上。降到 2% 仍能日采几千条覆盖 P95 趋势。

验证方法(黄金标准)

上一轮优化没生效就是因为相信 "加了 force-static 就行",没用 build 输出验证。这次用客观数据。

# 修前快照
pnpm build 2>&1 | tee /tmp/build-before.txt

# 修后跑同样命令
pnpm build 2>&1 | tee /tmp/build-after.txt

# 直接 diff 路由表
diff <(grep -E '^[┌├└] ' /tmp/build-before.txt) \
     <(grep -E '^[┌├└] ' /tmp/build-after.txt)

线上验证:

# Bot 路径应该 < 50ms(edge 返 404)
curl -so /dev/null -w "%{http_code} %{time_total}s\n" \
    https://involutionhell.com/wp-admin/

# 真业务路径应该看到 CDN HIT
curl -sI https://involutionhell.com/zh/events | grep -iE "x-vercel-cache|age"

24-48h 后 Vercel dashboard Fluid Active CPU 应从 7h54m → 预期 2-4h。

量化预期

  • /_not-found + bot blocklist: 拦截 ~30% scanner 流量 → 函数调用 -30%
  • 5 条 [locale]/* SSG: ~15-20% 调用归零
  • Sentry 0.1 → 0.02: 每次调用 CPU 开销 -5-10%

保守:CPU 7h54m → ~3-4h(贴近配额)
乐观:CPU < 2h(远低配额)

Follow-up(不在本 PR)

见 dev doc TODO 列表:/api/docs-tree build-time gen / /feed /rank searchParams 改 client / [username] ISR / admin edge 401 / Cloudflare proxy。

Test plan

  • pnpm build 修前/修后 diff 路由表,6 条 ƒ 翻转
  • 49/49 vitest 通过
  • Merge 后 24h 看 Vercel dashboard Fluid CPU 是否实测下降
  • curl bot 路径 < 50ms / 真路径 x-vercel-cache: HIT

## 背景
Hobby plan Fluid Active CPU 7h54m / 4h(198% 超额)。上一轮 SSR 优化
(commit 8517332,2026-05-06)只翻转了首页 1 条路由,剩 18 条 ƒ Dynamic
没碰。dev_docs/vercel-cpu-overage-2026-05.md 记录完整调查 + 修复 + 验证。

## 修复(每条都用 next build 输出验证翻转)

| 路由 | 修前 | 修后 |
|---|---|---|
| /_not-found              | ƒ | **○ Static**  ← scanner 不再烧 Fluid |
| /[locale]/docs           | ƒ | ● SSG |
| /[locale]/events         | ƒ | **● ISR 5m** ← revalidate=300 终于生效 |
| /[locale]/login          | ƒ | ● SSG |
| /[locale]/editor         | ƒ | ● SSG (cascade) |
| /[locale]/share          | ƒ | ● SSG (cascade) |

### H1: /_not-found 静态化
原 await getTranslations 让根 not-found ƒ Dynamic,所有 .env / wp-* /
graphql 漏洞扫描 + 真实 404 都烧 Fluid CPU。改成 hardcoded 双语,去掉
next-intl 依赖。

### H1b: proxy.ts edge bot blocklist
.php / wp-* / .env / graphql / werkzeug 等指纹直接在 edge 返 404,不进
Fluid 函数。明确不包含 login/admin 等真业务路径。

### H2: 给 [locale]/* pages 加 setRequestLocale + generateStaticParams
events / login: 加 params + setRequestLocale + generateStaticParams 让
revalidate=300 真正工作。
docs: 已有 setRequestLocale 但仍 ƒ,加 dynamic="force-static" 显式 opt-in。
events fetchEvents 改成失败降级返空(build 时后端不可达不挂 build)。

### H3: Sentry tracesSampleRate 0.1 → 0.02
server + edge 都改。10% 在月百万级请求量产生 10w+ traces 叠在 CPU 上。

## 验证(修复前/后 next build 输出 diff)

```
修前: 18 ƒ / 2 ● / 6 ○
修后: 20 ƒ / 7 ● / 7 ○
```

ƒ → ● / ○ 翻转 6 条。剩 ƒ 多是 auth-gated admin / searchParams 类,留作
follow-up(见 dev doc TODO 列表)。

49/49 vitest 通过。完整调查方法 + 量化预期写在 dev_docs/vercel-cpu-overage-2026-05.md。
Copilot AI review requested due to automatic review settings May 12, 2026 19:31
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
involutionhell-github-io Ready Ready Preview, Comment May 12, 2026 8:29pm
website-preview Ready Ready Preview, Comment May 12, 2026 8:29pm

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets Vercel Hobby plan Fluid CPU overage by flipping high-traffic routes from Dynamic SSR (ƒ) to Static/SSG/ISR (○/●) based on next build route table output, plus reducing per-request overhead in edge/server instrumentation.

Changes:

  • Reduce Sentry performance tracing sampling (server + edge) from 10% to 2%.
  • Add edge-level bot/vulnerability-scan early 404 handling and keep Leetcode legacy URL redirects ahead of i18n middleware.
  • Static/SSG/ISR-optimize key routes: root /_not-found (static), /[locale]/docs (force-static + static params), /[locale]/events (ISR + build-safe fetch), /[locale]/login (SSG).

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
sentry.server.config.ts Lowers server tracing sample rate to reduce runtime overhead.
sentry.edge.config.ts Lowers edge tracing sample rate to reduce per-request overhead in middleware/proxy.
proxy.ts Adds bot-scan path early-404 logic before Leetcode redirect + next-intl middleware.
generated/leetcode-slug-map.json Updates generated legacy slug → new slug mapping used by edge redirects.
dev_docs/vercel-cpu-overage-2026-05.md Documents the investigation method, root causes, fixes, and verification steps.
app/not-found.tsx Removes next-intl dependency to keep root not-found statically generated.
app/[locale]/login/page.tsx Adds setRequestLocale + generateStaticParams to make login SSG per locale.
app/[locale]/events/page.tsx Adds locale SSG/ISR plumbing and makes backend fetch build-failure tolerant.
app/[locale]/docs/page.tsx Forces static rendering and adds generateStaticParams for locale docs root.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread proxy.ts
Comment on lines +44 to +63
const BOT_PATH_PATTERNS = [
// PHP / 老 CMS:本站根本没装 PHP,所有 *.php 都是扫描
/\.php(?:$|[?#/])/i,
// wp-* 系列:WordPress 扫描
/\/wp-(admin|content|includes|login|json|config)(?:$|[/?#])/i,
// .env / config 文件直接探测
/\.env(?:\.[a-z]+)?(?:$|[?#/])/i,
/\/(config|settings|secrets|credentials)\.(yml|yaml|json|ini|toml|xml)(?:$|[?#])/i,
// GraphQL / GQL endpoint 扫描(本站没 GraphQL)
/\/(graphql|gql|api\/graphql|v\d+\/graphql)(?:$|[/?#])/i,
// Werkzeug / Flask debug console
/\/werkzeug\/console/i,
// 常见 admin / debug panels 探测路径(本站 admin 在 /[locale]/admin,
// 这些是其他平台特有路径,扫到必是 bot)
/\/(phpmyadmin|adminer|pma|dbadmin|mysqladmin)(?:$|[/?#])/i,
// git / svn 仓库文件暴露探测
/\/\.(git|svn|hg|bzr)\/(?:config|HEAD|entries)/i,
// 已知敏感文件(war/jar/key/pem)扫描
/\.(war|jar|sql|bak|key|pem|pfx)$/i,
];
用户 review 后指出"丢西瓜捡芝麻"风险,复盘 Vercel 30 天 dashboard 后修订:

## 真实根因(不是 /_not-found)

dashboard 30 天曲线显示 5/11 CPU 峰值 80-90min(基线 5-15min/day),完美
对应 SEO PR 落地时间:
- 5/11 15:01-15:39 UTC: PR #341 (253 MDX descriptions + 32 新 EN 翻译)
- 5/11 16:02-18:41 UTC: PR #342 (remark heading shift + leetcode dedup)
- 5/11 19:01-19:27 UTC: PR #343 + #340,4 小时 4 次 deploy 清空 ISR

加上 deploy.yml 里 IndexNow 主动告诉 Bing 重抓 → 5/10-5/12 crawler 风暴。
**这是 SEO 工作 successful 的代价,不是 bug。** 真实流量。

/_not-found 静态化 + bot blocklist 是真实 waste 清理(保留),但不能独立
解释 4× 激增。

## 撤回的两条 hack

1. Sentry tracesSampleRate 0.1 → 0.02:撤回,保持 10%
   observability 不能为这点 CPU 让步,10% 是行业标准,client/server/edge
   三处必须一致才能跨 runtime 串联 trace。

2. fetchEvents 失败一律返空:改成只在 NEXT_PHASE === phase-production-build
   时返空,运行时仍 throw 让 Sentry 抓真故障。否则 prod backend 挂了会被
   误显示成"暂无活动",掩盖故障。

## 保留的修复(best practice,不是 hack)

- /_not-found ƒ → ○:根 404 本就不需要 i18n
- proxy.ts bot blocklist:扫描器不该烧 Fluid
- /[locale]/docs /events /login 缺 setRequestLocale → 补:SSG/ISR 本就该工作
- /editor /share cascade ●:纯 client component,安全

## Build 验证

pnpm build 重跑:
- /[locale]/events 仍是 ● ISR 5m 1y
- [events] fetch failed at build, rendering empty shell(NEXT_PHASE guard 工作)
github-actions Bot and others added 2 commits May 12, 2026 19:51
## 砍掉的注释(被推回:"丢西瓜捡芝麻"叙事 / dev_docs 引用 / 历史回顾)

- app/not-found.tsx: 12 行 docstring → 1 行约束说明
- app/[locale]/events: ISR docstring + IS_BUILD 段落 → 单行 each
- app/[locale]/login: 删整段"SSG 化"docstring
- app/[locale]/docs: 删历史叙事,留 force-static 必要性说明
- proxy.ts: 删 dev_docs 引用 + 历史回顾,留 BOT_PATH 维护警告
- sentry.{server,edge}.config: 删"曾试过 2%"叙事,留三处一致约束

引用 dev_docs 文件路径 / PR 编号 / "原版/之前" 叙事都会 rot —— 全删,
真要找上下文用 git blame。

## 新增 CLAUDE.md(不重复 AGENT.md)

固化 5 条被 review 推回过的反模式:

1. 最佳实践 > 微小资源优化(不降 Sentry / 不藏 backend 错误 / 流量真增长就付费)
2. 路由分类必须用 next build 输出验证,不要凭感觉 + next-intl SSG 三条件
3. 注释规则项目特化(默认不写,禁 dev_docs/PR/历史引用)
4. CR 反馈直接改不分级问用户
5. generated/leetcode-slug-map.json 必须 commit 进 git 的特例

build 重跑 6 条路由翻转保持,无 regression。
Copilot 指出 matcher `.*\\..*` 排除所有含 `.` 的路径,所以 BOT_PATH_PATTERNS
里 .php / .env / .git/ / .(war|jar|sql|bak|key|pem|pfx) 等正则**从来不会被
执行**,是死代码。

实际行为没问题——这些 dot-path scanner 直接走到 Next 默认 404 → 命中
○ Static /_not-found,由 CDN-served,不烧 Fluid。但写在 regex 列表里给人
"已在 edge 早返"的错觉。

删 5 条死规则,留下 4 条无 dot 真正生效的:wp-* / graphql / werkzeug /
phpmyadmin。注释里写明 dot-path 不要再加(已被 static 404 兜底)。

Co-authored-by: copilot-pull-request-reviewer[bot] <copilot-pull-request-reviewer[bot]@users.noreply.github.com>
@longsizhuo longsizhuo merged commit 6401d37 into main May 12, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants