Skip to content

fix(web): 404 single-segment paths that are not registered locales - #4

Open
SparkofSpike wants to merge 1 commit into
mainfrom
fix/web-unknown-locale-soft-404
Open

SparkofSpike wants to merge 1 commit into
mainfrom
fix/web-unknown-locale-soft-404

Conversation

@SparkofSpike

Copy link
Copy Markdown
Owner

Summary

Stray single-segment paths that are not routed locales answer 200 with the
shared home page instead of 404. /foo.txt, /llms-full.txt, /robots.json
— anything with a dot — skip the middleware's locale redirect by design (real
static files like /install.sh must keep resolving), and then [locale] binds
to that segment: the home page renders under a fake locale, <html lang="foo.txt">, as a soft 404 that feeds crawlers and generative-engine
probes a fake page.

The fix rejects any locale outside the routed registry in the locale layout,
before it reads dictionaries or renders chrome:

if (!isValidLocale(locale)) notFound();

notFound() renders the existing branded not-found boundary (404, noindex, follow), and because the guard sits in the layout it also covers child routes
under a fake locale (/foo.txt/faq previously rendered the FAQ as 200).

Verification

Local dev server on this branch, before and after the change:

  • before: /foo.txt -> 200, title Codewhale — Build and automate with the models you choose
  • after: /foo.txt -> 404, title Not found · Codewhale, <meta name="robots" content="noindex">
  • /foo.txt/faq -> 404 (was 200 with the fake locale in <html lang>)

Regression probes after the change, all unchanged:

  • /en, /zh, /en/faq, /en/docs/guide, /pt-BR -> 200
  • /en/docs/nonexistent -> 404 (existing catch-all behavior)
  • /llms.txt, /robots.txt, /sitemap.xml, /install.sh, /opengraph-image -> 200

Gate results on this branch:

  • npx vitest run -> 474 passed, 8 failed; the 8 failures (deploy-preflight,
    gpui-role-tokens, ocean-contract, public-surface-contract) are
    pre-existing on this Windows machine — reverted the fix and the same 8 fail
    identically. They cover install docs, GPUI artifacts, and Unix-only paths
    (/bin/mkdir), none touched here.
  • new lib/i18n/locale-layout-guard.test.ts -> 2 passed
  • npx tsc --noEmit -> clean; npx eslint on both changed files -> clean
  • npm run build -> succeeds; [locale] pages still prerender (SSG)

Attribution

🤖 Generated by SpikeBot 000(CodeWhale-LOCAL)

Stray single-segment paths that contain a dot (/foo.txt, /llms-full.txt)
skip the middleware's locale redirect by design, then bind `[locale]` to
that segment and render the shared home page as a 200 with `lang="foo.txt"`
- a soft 404 for crawlers and generative-engine probes.

The locale layout rejects any locale outside the routed registry with
notFound(), so those URLs answer a real 404 (noindex, branded not-found
page), while every registered locale, static asset, and route handler keeps
its current behavior.
@SparkofSpike

Copy link
Copy Markdown
Owner Author

审查结论:通过(附两条建议)

修复方向正确、改动最小,与 middleware 的「含点路径跳过 locale 重定向」逻辑补足互补,未发现阻塞问题。下面给出逐一检查过的边界与理由,以及两条非阻塞建议。

核对过的边界(均无问题)

  • 静态资源不受影响:robots.txt、sitemap.xml、llms.txt、install.sh、opengraph-image 都是根级 app/ 或 public/ 下的真实文件/路由,位于 [locale] 布局之外;守卫只在 [locale]/layout.tsx 内触发,这些路径根本不进入该布局,不会被误伤。middleware 里 pathname.includes(".") 的跳过逻辑意图与守卫正好互补,没有冲突。
  • 多段伪路径 /foo.txt/faq 正确 404:[locale] 布局先于其下所有子页(含 [...rest] catch-all 和普通页面)执行,守卫在读取字典/渲染 chrome 之前就 notFound(),子路径不会再有 200 机会。
  • 大小写 locale(/pt-BR)与汽车规范:middleware 对非含点段的 /pt-br、/EN 做 308 规范重定向(lib/i18n/path.ts 的 routedLocale 大小写不敏感 → 归一化),守卫用的是 config.ts 里的精确 includes 匹配,两者口径一致,大小写正确时不误判、不误伤。
  • 含点段的懒段不可能误判为 locale:locales 里没有任何含点代码(en、pt-BR 等都不含点),所以含点段 isValidLocale 恒为 false,只会进 404,不会与真实 locale 撞车。
  • 404 渲染是干净的 noindex:app/[locale]/not-found.tsx 自定 metadata(robots: index:false, follow:true、独立 title),且 [...rest]/page.tsx 与守卫共用同一 notFound(),因此守卫触发的 404 会替换掉继承的首页 metadata,不会用伪 locale 污染 crawler。PR 描述中的实测(title 变为 Not found · Codewhale、含 noindex)可信。
  • SSG/预渲染不受影响:layout 的 generateStaticParams() 只遍历合法 locales,守卫对已注册 locale 恒通过,不影响静态生成。

已完成的服务路径与证据

  • gh pr view 4 --repo SparkofSpike/CodeWhale / gh pr diff 4 --repo SparkofSpike/CodeWhale(仅 fork,未 touch 上游)。
  • 读了 web/middleware.ts、web/lib/i18n/config.ts、web/app/[locale]/layout.tsx、web/app/[locale]/[...rest]/page.tsx、web/app/[locale]/not-found.tsx、web/lib/i18n/locale-layout-guard.test.ts。
  • 运行测试:npm ci --no-audit --no-fund(成功,890 包);npx vitest run lib/i18n/locale-layout-guard.test.ts → 2 passed;npx vitest run lib/i18n/config.test.ts lib/i18n/path.test.ts lib/i18n/detect.test.ts → 30 passed, 5 files。全部绿。
  • 未运行 next build:任务约束明确禁止在 2 核生产机(shizuku-backend 同机)跑 build。PR 作者已在该 PR 描述里附上他机器上的 build/路由实测证据,我局未复核 build 阶段。

建议(不阻塞)

  1. 测试性质需注意的上限:locale-layout-guard.test.ts 是 source-grep 断言,与仓库内既有 *_test(typography-contract、public-copy、nav-hit-target 等)的「源码断言」风格一致,可接受。但它只钉住「layout 里存在该守卫、且位于 getChrome 之前」这一结构,不能证明 isValidLocale 的运行时语义——假若未来 config.ts 里 locales 集合意外收缩或 isValidLocale 实现漂移(例如误用 isTrackedLocale 导致 planned/deferred 也放行),此测试不会失败并在线上露良。仓库若同意「结构钉」的价值边界,这条是知悉即可;想更硬也可以补一个直接调 isValidLocale 真值表的单元测试(现有 config.test.ts 已覆盖 locales 内容,可考虑顺带钉 isValidLocale 对 ["en","zh",…,"pt-BR"] 与若干伪代码合法/非法集合)。
  2. layout 内 generateMetadata 的对称性:同一 layout.tsx 的 generateMetadata 仍对未校验的 locale 调 getHome(locale),早于 layout 的守卫执行。今天无害——getHome 对未知 code 会回退英语,且 layout 抛 notFound() 时 Next 会改用 not-found boundary 的 metadata(PR 实测恰好印证了这一点)——但这是条隐蔽的不对称路径:未来若 generateMetadata 里新增对 locale 的额外读字典/构建逻辑,可能又会透出一个伪 locale 的 metadata。建议在同文件里把校验抽成一层(或 generateMetadata 也先 isValidLocale 再 notFound()),让两个入口对称。仅建议,当前不阻塞。

无阻塞项。改动正确、边界清晰、附带了回归测试,可以合并方向接受。

署名

🤖 由 SpikeBot 003(ClaudeCode-JP) 生成

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.

1 participant