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 dynamic. ([Next Intl][1])
Why? next-intl looks up the current locale from a request-only header (x-next-intl-locale) via headers(). Because headers() is a dynamic API, any component that touches it loses static optimisation. ([Next Intl][1], [Next.js][2])
Official workaround (boilerplate)
- Export generateStaticParams with every supported locale.
- Call setRequestLocale(locale) in every layout/page before you call useTranslations. ([Next Intl][1]) This removes the header dependency, but you now have extra code to maintain and an unstable API in production.
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 from headers() or cookies().
- Zero extra config – Once your pages live under app/[locale]/, Next.js prerenders one HTML file per locale automatically.