✨ Move locale into a [locale] route segment so tenant pages render static/ISR#737
✨ Move locale into a [locale] route segment so tenant pages render static/ISR#737joshbermanssw wants to merge 8 commits into
Conversation
… with tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-safe tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…and data helpers Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
suiyangqiu
left a comment
There was a problem hiding this comment.
A couple of code-craft notes from a review pass, both non-blocking. Left inline on the relevant lines.
| } | ||
| } | ||
|
|
||
| export async function generateStaticParams() { |
There was a problem hiding this comment.
generateStaticParams is copy-pasted across three siblings. This block, plus the equivalents in docs/[slug]/page.tsx and [filename]/page.tsx, are structurally identical: query a *Connection, then map edges to { locale: localeFromBreadcrumbs(bc), product: bc[0], <leaf>: filename }. Only the client query and the leaf key name differ. The page.tsx/blog/page.tsx/docs/page.tsx index pages also repeat the same seen/params dedup loop verbatim.
Worth pulling into one helper in utils/, e.g. staticParamsFromConnection(edges, leafKey), so each page becomes a one-liner and there is a single place to fix a future copy-paste bug. Non-blocking.
|
|
||
| const data = await getPageData(product, relativePath, branch); | ||
| // English-only preview/fallback route; branch is the 4th positional arg. | ||
| const data = await getPageData(product, relativePath, "en", branch); |
There was a problem hiding this comment.
Inconsistent page-data call signatures. getPageData(product, relativePath, "en", branch) and getBlogPageData are positional with a defaulted 3rd locale arg, while getDocPageData({ product, slug, locale }) and the get*WithFallback helpers take an options object. The ("en", branch) positional call here is the fragile seam - easy to pass an arg in the wrong slot.
Since these signatures are already being touched in this PR, worth standardising on the object form (getPageData({ product, filename, locale, branch })) across the page-data helpers. Non-blocking.
TL;DR
The whole tenant site renders dynamically on every request — the shared layout (
app/[product]/layout.tsx) callsgetLocale()→headers(), which opts every route into dynamic rendering, so each crawler/bot hit runs a server-side Tina/GraphQL query (~0.6s). This moves locale into a[locale]route segment sourced fromparamsinstead of a header, so the homepage and content pages become static/ISR (CDN-cacheable). Public URLs are unchanged.Why
Structural follow-up to #735 (the quick-win cache PR is #736). Function Duration is dominated by re-rendering for automated traffic. A page can only be CDN-cached per-locale if locale comes from the route, not a request header — hence the
[locale]segment.Build output confirms the flip from dynamic → SSG/ISR:
/[locale]/[product](homepage)ƒdynamic●SSG, revalidate 1h/[locale]/[product]/blog,blog/[slug],docs,docs/[slug]ƒdynamic●SSG/ISR/[locale]/[product]/[filename]ƒ(via dynamic layout)●SSG, revalidate 1hpnpm buildsucceeds; 103 static pages generated, including both/en/YakShaver/blogand/zh/YakShaver/blog(zh dimension works).How
app/[product]/**→app/[locale]/[product]/**(git renames, history preserved).resolveRequestRoute—yakshaver.cn/x→/zh/YakShaver/x,ssw.com.au/x→/en/SSW/x, local/zh/x→/zh/<default>/x. Rewrite, not redirect — public URLs are byte-identical.localefromparams;getLocale()and thex-languageheader are deleted.generateStaticParamsgains alocaledimension vialocaleFromBreadcrumbs(en for all tenants; zh additionally for YakShaver, the only tenant with zh content).[filename]ISR uncapped to 1h — raisedrevalidate: 10→3600on the inner Tina fetch in bothgetPageDataand the page'sgenerateMetadata(Next uses the lowest revalidate across a route, so therevalidate = 3600export was previously capped to 10s). Build-confirmed at 1h.graph LR R[Request host+path] --> M{middleware} M -->|resolveRequestRoute| P["/locale/product/..."] P --> T["app/[locale]/[product] (SSG/ISR)"]Files to review (start here):
utils/resolveRequestRoute.ts(start here)middleware.tsutils/localeFromBreadcrumbs.tsgenerateStaticParamsdecides en vs zh from Tina breadcrumbs.app/[locale]/[product]/layout.tsxheaders()read.Reviewer notes
yakshaver.aiserves en and a.cnhost serves zh, and the language toggle round-trips./zhprefix); the/zhprefix is local/staging only — matches existingLanguageToggle/environment.tsbehavior.page-dataAPI callers updated. The data-helper signatures gained alocalearg; the cookie-based preview routes now pass("en", branch)positionally (they're English-only fallback routes).privacy/feedback/sitemap.xmlstay dynamic (nogenerateStaticParams) — low-traffic; can be made static in a follow-up.Tests
utils/resolveRequestRoute.ts(9 cases) andutils/localeFromBreadcrumbs.ts(4 cases) are unit-tested.pnpm testgreen (18).pnpm exec tsc --noEmitclean.pnpm buildsucceeds with the route table above.Links