@@ -13,12 +13,14 @@ deepened: 2026-01-23
1313** Sources:** Vercel React Best Practices, 3 reviewer agents (DHH, Kieran, Simplicity)
1414
1515### Key Improvements
16+
16171 . Expanded scope to include ALL locale-dependent files (18 files, not 3)
17182 . Added Vercel-specific caching guidance (LRU vs React.cache)
18193 . Fixed ` generateStaticParams ` issues for CN route
19204 . Added client-side utility migrations
2021
2122### Critical Findings from Reviewers
23+
2224- Home page ALSO uses ` searchParams ` - must fix
2325- ` useLocale.ts ` uses ` useSearchParams() ` - needs migration to ` usePathname() `
2426- Dual encoding (` /cn/...?locale=cn ` ) is redundant - remove query params entirely
@@ -32,17 +34,19 @@ Docs pages have **0% cache hit rate** causing $50+ Vercel overage. Every request
3234## Problem Statement
3335
3436** Symptoms from Vercel dashboard:**
37+
3538- 0% cache hit rate on ` /docs/[[...slug]] `
3639- $22.84 Fluid Active CPU (178 hours)
3740- $20.43 Fast Origin Transfer (338 GB)
3841- 99.5% traffic from single region (iad1)
3942- Spike started ~ Jan 7
4043
4144** Root Cause:**
45+
4246``` tsx
4347// apps/www/src/app/(app)/docs/[[...slug]]/page.tsx:35-37
4448type DocPageProps = {
45- searchParams: Promise <{ locale: string }>; // <-- THIS FORCES DYNAMIC RENDERING
49+ searchParams: Promise <{ locale: string }>; // <-- THIS FORCES DYNAMIC RENDERING
4650};
4751```
4852
@@ -55,6 +59,7 @@ From Vercel's React Best Practices guide (Section 3.3 - Cross-Request LRU Cachin
5559> "React.cache() only works within one request. For data shared across sequential requests, use an LRU cache."
5660
5761However, for ** static documentation sites** , the better approach is:
62+
58631 . ** Static Generation** via ` generateStaticParams() ` - already exists but bypassed
59642 . ** Remove dynamic triggers** - ` searchParams ` forces dynamic mode
60653 . ** Vercel Edge CDN** will cache static pages automatically
@@ -66,6 +71,7 @@ The current `React.cache()` calls in [registry-cache.ts](apps/www/src/lib/regist
6671Move locale from query param (` ?locale=cn ` ) to path segment (` /cn/docs/... ` ).
6772
6873** Why this approach:**
74+
6975- Enables full static generation + CDN caching
7076- Better SEO (separate URLs for each locale)
7177- Follows Next.js i18n best practices
@@ -75,16 +81,16 @@ Move locale from query param (`?locale=cn`) to path segment (`/cn/docs/...`).
7581
7682### Files to Modify (Complete List)
7783
78- | File | Change |
79- | ------| --------|
80- | ` apps/www/src/app/(app)/docs/[[...slug]]/page.tsx ` | Remove ` searchParams ` , add locale prop |
81- | ` apps/www/src/app/(app)/page.tsx ` | Remove ` searchParams ` (home page!) |
82- | ` apps/www/src/app/cn/docs/[[...slug]]/page.tsx ` | NEW - CN docs route |
83- | ` apps/www/src/app/cn/page.tsx ` | NEW - CN home page |
84- | ` apps/www/src/hooks/useLocale.ts ` | Use ` usePathname() ` not ` useSearchParams() ` |
85- | ` apps/www/src/lib/withLocale.ts ` | Remove ` ?locale=cn ` suffix |
86- | ` apps/www/src/components/languages-dropdown-menu.tsx ` | Remove query param setting |
87- | ` apps/www/next.config.ts ` | Replace rewrites with redirects |
84+ | File | Change |
85+ | ----------------------------------------------------- | ------------------------------------------- |
86+ | ` apps/www/src/app/(app)/docs/[[...slug]]/page.tsx ` | Remove ` searchParams ` , add locale prop |
87+ | ` apps/www/src/app/(app)/page.tsx ` | Remove ` searchParams ` (home page!) |
88+ | ` apps/www/src/app/cn/docs/[[...slug]]/page.tsx ` | NEW - CN docs route |
89+ | ` apps/www/src/app/cn/page.tsx ` | NEW - CN home page |
90+ | ` apps/www/src/hooks/useLocale.ts ` | Use ` usePathname() ` not ` useSearchParams() ` |
91+ | ` apps/www/src/lib/withLocale.ts ` | Remove ` ?locale=cn ` suffix |
92+ | ` apps/www/src/components/languages-dropdown-menu.tsx ` | Remove query param setting |
93+ | ` apps/www/next.config.ts ` | Replace rewrites with redirects |
8894
8995### Step 1: Fix English Docs Page
9096
@@ -96,22 +102,22 @@ Remove `searchParams` from page props:
96102// BEFORE
97103type DocPageProps = {
98104 params: Promise <{ slug: string [] }>;
99- searchParams: Promise <{ locale: string }>; // DELETE THIS
105+ searchParams: Promise <{ locale: string }>; // DELETE THIS
100106};
101107
102108// AFTER
103109type DocPageProps = {
104110 params: Promise <{ slug: string [] }>;
105- locale? : ' en ' | ' cn ' ; // Optional prop, defaults to 'en'
111+ locale? : " en " | " cn " ; // Optional prop, defaults to 'en'
106112};
107113
108- export const dynamic = ' force-static' ;
114+ export const dynamic = " force-static" ;
109115
110116// Update getDocFromParams
111- async function getDocFromParams({ params , locale = ' en ' }: DocPageProps ) {
117+ async function getDocFromParams({ params , locale = " en " }: DocPageProps ) {
112118 const slugParam = (await params ).slug ;
113119 // Use locale prop instead of searchParams
114- if (locale === ' cn ' ) {
120+ if (locale === " cn " ) {
115121 // Chinese logic...
116122 }
117123 // ...
@@ -122,22 +128,26 @@ async function getDocFromParams({ params, locale = 'en' }: DocPageProps) {
122128
123129``` tsx
124130// apps/www/src/app/cn/docs/[[...slug]]/page.tsx
125- import { DocContent } from ' @/app/(app)/docs/[[...slug]]/doc-content' ;
126- import { allDocs } from ' contentlayer/generated' ;
131+ import { DocContent } from " @/app/(app)/docs/[[...slug]]/doc-content" ;
132+ import { allDocs } from " contentlayer/generated" ;
127133// ... other imports from main page
128134
129- export const dynamic = ' force-static' ;
135+ export const dynamic = " force-static" ;
130136
131137// IMPORTANT: Generate params for CN docs specifically
132138export function generateStaticParams() {
133139 return allDocs
134- .filter ((doc ) => doc ._raw .sourceFileName ?.endsWith (' .cn.mdx' ))
140+ .filter ((doc ) => doc ._raw .sourceFileName ?.endsWith (" .cn.mdx" ))
135141 .map ((doc ) => ({
136- slug: doc .slugAsParams .replace (/ \. cn$ / , ' ' ).split (' / ' ).slice (1 ),
142+ slug: doc .slugAsParams .replace (/ \. cn$ / , " " ).split (" / " ).slice (1 ),
137143 }));
138144}
139145
140- export default async function CNDocPage({ params }: { params: Promise <{ slug: string [] }> }) {
146+ export default async function CNDocPage({
147+ params ,
148+ }: {
149+ params: Promise <{ slug: string [] }>;
150+ }) {
141151 // Render with locale='cn'
142152 // ... (copy rendering logic with locale hardcoded to 'cn')
143153}
@@ -149,20 +159,20 @@ export default async function CNDocPage({ params }: { params: Promise<{ slug: st
149159// apps/www/src/hooks/useLocale.ts
150160
151161// BEFORE - forces client-side hydration issues
152- import { useSearchParams } from ' next/navigation' ;
162+ import { useSearchParams } from " next/navigation" ;
153163
154164export const useLocale = () => {
155165 const searchParams = useSearchParams ();
156- const locale = searchParams ?.get (' locale' ) || ' en ' ;
166+ const locale = searchParams ?.get (" locale" ) || " en " ;
157167 return locale ;
158168};
159169
160170// AFTER - derive from pathname
161- import { usePathname } from ' next/navigation' ;
171+ import { usePathname } from " next/navigation" ;
162172
163173export const useLocale = () => {
164174 const pathname = usePathname ();
165- return pathname ?.startsWith (' /cn' ) ? ' cn ' : ' en ' ;
175+ return pathname ?.startsWith (" /cn" ) ? " cn " : " en " ;
166176};
167177```
168178
@@ -173,15 +183,15 @@ export const useLocale = () => {
173183
174184// BEFORE - adds redundant query param
175185export const hrefWithLocale = (href : string , locale : string ) => {
176- if (locale === ' cn ' ) {
177- return ` /cn${href }?locale=${locale } ` ; // Redundant!
186+ if (locale === " cn " ) {
187+ return ` /cn${href }?locale=${locale } ` ; // Redundant!
178188 }
179189 return href ;
180190};
181191
182192// AFTER - path only
183193export const hrefWithLocale = (href : string , locale : string ) => {
184- if (locale === ' cn ' ) {
194+ if (locale === " cn " ) {
185195 return ` /cn${href } ` ;
186196 }
187197 return href ;
@@ -234,15 +244,15 @@ export default async function IndexPage({
234244}: {
235245 searchParams: SearchParams ;
236246}) {
237- const locale = ((await searchParams ).locale || ' en ' ) as keyof typeof i18n ;
247+ const locale = ((await searchParams ).locale || " en " ) as keyof typeof i18n ;
238248 // ...
239249}
240250
241251// AFTER - remove searchParams, default to 'en'
242- export const dynamic = ' force-static' ;
252+ export const dynamic = " force-static" ;
243253
244254export default async function IndexPage() {
245- const locale = ' en ' ; // English home page
255+ const locale = " en " ; // English home page
246256 // ...
247257}
248258
@@ -273,12 +283,12 @@ export default async function IndexPage() {
273283
274284## Alternative Approaches Considered
275285
276- | Approach | Pros | Cons |
277- | ----------| ------| ------|
278- | Path segments (chosen) | Full caching, SEO-friendly | Route restructuring needed |
279- | Cookies for locale | No URL changes | Still dynamic, no caching |
280- | ISR with short TTL | Quick fix | Still hits origin frequently |
281- | Remove CN support | Simplest | Loses Chinese users |
286+ | Approach | Pros | Cons |
287+ | ---------------------- | -------------------------- | ---------------------------- |
288+ | Path segments (chosen) | Full caching, SEO-friendly | Route restructuring needed |
289+ | Cookies for locale | No URL changes | Still dynamic, no caching |
290+ | ISR with short TTL | Quick fix | Still hits origin frequently |
291+ | Remove CN support | Simplest | Loses Chinese users |
282292
283293## Risk Analysis
284294
@@ -289,13 +299,15 @@ export default async function IndexPage() {
289299## References
290300
291301### Internal Files
292- - [ apps/www/src/app/(app)/docs/[[ ...slug]] /page.tsx] ( apps/www/src/app/(app)/docs/[[...slug]]/page.tsx ) - Docs page
293- - [ apps/www/src/app/(app)/page.tsx] ( apps/www/src/app/(app)/page.tsx ) - Home page
302+
303+ - [ apps/www/src/app/(app)/docs/[[ ...slug]] /page.tsx] ( < apps/www/src/app/(app)/docs/[[...slug]]/page.tsx > ) - Docs page
304+ - [ apps/www/src/app/(app)/page.tsx] ( < apps/www/src/app/(app)/page.tsx > ) - Home page
294305- [ apps/www/src/hooks/useLocale.ts] ( apps/www/src/hooks/useLocale.ts ) - Client locale hook
295306- [ apps/www/src/lib/withLocale.ts] ( apps/www/src/lib/withLocale.ts ) - Locale link helper
296307- [ apps/www/next.config.ts] ( apps/www/next.config.ts ) - Next.js config
297308
298309### External Documentation
310+
299311- [ Next.js i18n Routing] ( https://nextjs.org/docs/app/building-your-application/routing/internationalization )
300- - [ Vercel React Best Practices - Server Caching] ( /.claude/rules /vercel-react-best-practices/AGENTS.md#33-cross-request-lru-caching )
312+ - [ Vercel React Best Practices - Server Caching] ( /.claude/skills /vercel-react-best-practices/AGENTS.md#33-cross-request-lru-caching )
301313- [ Next.js Static Generation] ( https://nextjs.org/docs/app/building-your-application/rendering/server-components#static-rendering-default )
0 commit comments