Static vs Dynamic Rendering with i18n in Next.js
The issue with next-intl
What happens? When you use
useTranslations,getTranslations, or any next-intl helper inside a Server Component on an i18n-routed app (/en/…,/fr/…), Next.js marks the whole route as dynamic. ([Next Intl][1])Why? next-intl looks up the current locale from a request-only header (
x-next-intl-locale) viaheaders(). Becauseheaders()is a dynamic API, any component that touches it loses static optimisation. ([Next Intl][1], [Next.js][2])Official workaround (boilerplate)
- Export
generateStaticParamswith every supported locale. - Call
setRequestLocale(locale)in every layout/page before you calluseTranslations. ([Next Intl][1]) This removes the header dependency, but you now have extra code to maintain and an unstable API in production.
- Export
How intlayer sidesteps the problem
Design choices
- Route-param only – The locale comes from the
[locale]URL segment that Next.js already passes to every page. - Compile-time bundles – Translations are imported as regular ES modules, so they’re tree-shaken and embedded at build-time.
- No dynamic APIs –
useT()reads from React context, not fromheaders()orcookies(). - Zero extra config – Once your pages live under
app/[locale]/, Next.js prerenders one HTML file per locale automatically.