I run bacotto , a B2B sales-list SaaS for the Japanese market. It started Japanese-only. Last week I made the whole thing bilingual — landing page, dashboard, auth, emails, ~17 blog posts, and 4,364 programmatic SEO pages. Here's the real write-up: the architecture, and the things that broke.
The setup
Next.js 15 (App Router), TypeScript, React 19
~1,565 hard-coded Japanese strings across 40+ files
4,364 statically-generated programmatic SEO pages ( force-static , dynamicParams = false )
Goal: Japanese stays the default at / , English served from /en , no SEO regression
Why next-intl
I evaluated next-intl, Paraglide, and rolling my own. next-intl won because:
App Router-native, works with Server Components without "use client" thrash
Supports static generation — critical for the 4,364 force-static pages
Built-in middleware for locale negotiation + hreflang helpers
Paraglide is leaner, but with only two locales its tree-shaking edge didn't matter.…