Skip to content

fix(profile): /u/[username] SSR 抓取加重试 + 添加 error 边界#286

Merged
longsizhuo merged 4 commits intomainfrom
fix/profile-ssr-error-boundary
Apr 16, 2026
Merged

fix(profile): /u/[username] SSR 抓取加重试 + 添加 error 边界#286
longsizhuo merged 4 commits intomainfrom
fix/profile-ssr-error-boundary

Conversation

@longsizhuo
Copy link
Copy Markdown
Member

现象

线上访问自己的个人主页(/u/{githubId})偶发报白屏:

Application error: a server-side exception has occurred while loading involutionhell.com (see the server logs for more information).
Digest: 3734429282

定位

Vercel runtime logs 抓到的是:

```
18:29:44 GET /u/114939201 500 Error: profile backend 403
18:36:40 GET /u/114939201 500 Error: profile backend 403
```

但从任意其他位置 `curl https://api.involutionhell.com/api/user-center/profile/114939201\` 都是 200,后端 SaToken 配置里 `/api/user-center/profile/**` 也在白名单。

结论:单次 403 来自 Cloudflare 对 Vercel SSR 出口的偶发拦截(疑似 Bot Fight / Managed Challenge),不是后端本身问题。叠加 `fetch` 的 `next: { revalidate: 300 }`,一次 403 会被 Next Data Cache 粘住 5 分钟,把问题放大成"持续白屏"。

修复

  1. `app/u/[username]/page.tsx` — fetchProfile 带重试 + 诊断日志

    • 首次走 ISR(300s),非 2xx 后两次 `cache: "no-store"` 重试(300ms / 800ms 退避),绕开 Data Cache 把错误粘住的问题
    • 显式 UA `InvolutionHell-SSR/1.0` + `accept: application/json`,降低被 CF 判 bot 概率
    • 每次非 2xx 都 `console.warn`:status / cf-ray / cf-mitigated / content-type / 响应体前 300 字符 — 下次再发生时 Vercel 日志直接有证据
    • 404 仍然走 `notFound()`(不重试),500 / 403 / 网关异常才重试
  2. `app/u/[username]/error.tsx` — 新增路由级 error 边界

    • 取代 Next 默认白屏,给"重试 / 返回首页 / 排行榜"三个出口
    • 展示 digest,用户回贴就能定位

不在这个 PR 里的事

  • 后端本身不需要改(白名单已正确,直连 200)
  • Cloudflare 侧的长期方案(放行 Vercel IP / 关掉 profile 路径的 Bot Fight)需要有 zone admin 权限,后续开 issue 跟
  • 没碰其他路由的错误边界(/settings、/rank 等),本次聚焦 /u/[username]

Test plan

  • `pnpm tsc --noEmit` 对改动文件无报错
  • 合并部署后手动访问 /u/114939201 验证正常 200
  • 观察 Vercel runtime logs 1-2 天,下次出现 403 时能在 `[fetchProfile]` warn 里看到 cf-ray / body 片段
  • 人为造一次错误(临时改 BACKEND_URL 到 404 域名)确认 error.tsx 渲染"个人主页暂时加载失败"而不是 Next 默认页

线上偶发"Application error: a server-side exception has occurred"报错(digest 3734429282)。
Vercel runtime logs 定位到 GET /api/user-center/profile/{id} 收到 Cloudflare 403(直连后端 200),
疑似 Vercel SSR 出口被 CF Bot/Managed Challenge 偶发拦截。单次 403 直接冒泡到 Next 默认错误页,
用户体验就是白屏 + digest。

两处改动:

1) app/u/[username]/page.tsx — fetchProfile 加重试 + 诊断日志
   - 首次走 ISR(300s),失败后两次 cache:"no-store" 重试(300ms/800ms 退避),绕开 Data Cache
     把 403 粘住 5 分钟的问题
   - 显式带 UA / Accept header,降低被 CF 判 bot 概率
   - 每次非 2xx 都记录 status / cf-ray / cf-mitigated / content-type / 响应体前 300 字符,
     下次再发生时 Vercel 日志里直接有证据(原先只在 dev 打 warn)

2) app/u/[username]/error.tsx — 新增路由级错误边界
   - 取代 Next 默认的白屏错误页,给出"重试 / 返回首页 / 排行榜"三个出口
   - 展示 digest 让用户可以回贴排查
Copilot AI review requested due to automatic review settings April 16, 2026 18:41
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 16, 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 Apr 16, 2026 7:32pm
website-preview Canceled Canceled Apr 16, 2026 7:32pm

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

该 PR 旨在修复 /u/[username] 个人主页在 Vercel SSR 场景下偶发被 Cloudflare 返回 403 导致的“白屏/500 并被 ISR 缓存粘住”问题,并通过路由级 error boundary 提供可恢复的降级 UI。

Changes:

  • 为 profile SSR 抓取增加重试(首次走 ISR,失败后两次 no-store 重试)并补充诊断日志(cf-ray/响应体片段等)
  • 新增 /u/[username] 路由级 error.tsx,替代 Next 默认错误页,提供“重试/返回首页/排行榜”出口

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
app/u/[username]/page.tsx fetchProfile 增加重试策略、生产日志与 CF 诊断信息,减少偶发 403 被缓存放大的影响
app/u/[username]/error.tsx 新增路由级错误边界,避免默认 Application error 白屏并提供重试能力

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

Comment thread app/u/[username]/page.tsx Outdated
Comment thread app/u/[username]/page.tsx
Comment on lines +195 to +199
const bodySnippet = await res
.text()
.then((t) => t.slice(0, 300))
.catch(() => "<read body failed>");
warnFetchProfile("backend non-2xx", {
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The code logs a bodySnippet (first 300 chars) from upstream responses into server logs. Even though this only happens on non-2xx, those bodies can still contain sensitive data (e.g., backend error pages, stack traces, Cloudflare challenge HTML). Consider redacting/sanitizing this snippet, logging only when content-type is safe/expected, or logging a hash/length instead of raw body content.

Copilot uses AI. Check for mistakes.
Comment thread app/u/[username]/error.tsx Outdated
Comment thread app/u/[username]/error.tsx Outdated
longsizhuo and others added 3 commits April 17, 2026 02:50
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@longsizhuo longsizhuo merged commit 4f0ff43 into main Apr 16, 2026
3 of 5 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