next-intl-setRequestLocale-ssg-fix
next-intl setRequestLocale SSG Fix
Problem
When using next-intl v4+ with Next.js App Router and static generation
(generateStaticParams), locale pages build successfully but render text in the
default locale (typically English) instead of the expected locale. The build
output shows no errors — pages are generated for all locales — but at runtime,
all locale variants show identical (default) content.
Context / Trigger Conditions
- Using
next-intlv4.x with Next.js 14/15 App Router - Pages use
generateStaticParams()to pre-render locale variants [locale]dynamic segment in the app directory- Symptom 1: Build succeeds, locale pages are listed in build output (e.g.,
/zh-CN,/zh-TW) - Symptom 2: Page
<title>may show correct locale (ifgenerateMetadatapasses locale explicitly viagetTranslations({ locale })) - Symptom 3: Client component text (from
useTranslations()) shows default locale - Symptom 4:
getMessages()in layout returns default locale messages - No error messages — the fallback is silent
Especially likely when:
- Using a custom middleware (not
createMiddlewarefromnext-intl/middleware) - The middleware handles auth (e.g., Supabase) alongside locale detection
Root Cause
During static site generation (SSG), there is no HTTP request context. The
requestLocale parameter in getRequestConfig resolves to undefined because
no middleware sets the x-next-intl-locale header, and there's no live request
to extract the locale from.
When requestLocale is undefined, the getRequestConfig function typically
falls back to the default locale:
// i18n/request.ts
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
// requestLocale is undefined during SSG!
if (!locale || !locales.includes(locale)) {
locale = 'en'; // Always falls back to default
}
// ...
});
This means getMessages() always returns default locale messages during SSG.
Solution
Call setRequestLocale(locale) in every server component (layout and page)
that uses next-intl server functions, before calling getMessages(),
getTranslations(), or any other next-intl/server function.
Fix the layout:
// app/[locale]/layout.tsx
import { getMessages, setRequestLocale } from 'next-intl/server';
export default async function LocaleLayout({ children, params }) {
const { locale } = params;
// CRITICAL: Set locale for static rendering context
setRequestLocale(locale);
const messages = await getMessages(); // Now returns correct locale messages
// ...
}
Fix every page:
// app/[locale]/page.tsx
import { setRequestLocale } from 'next-intl/server';
export default function LocalePage({ params }) {
setRequestLocale(params.locale);
return <PageClient />;
}
// app/[locale]/about/page.tsx
import { getTranslations, setRequestLocale } from 'next-intl/server';
export default async function LocaleAboutPage({ params }) {
const { locale } = params;
setRequestLocale(locale);
const t = await getTranslations({ locale, namespace: 'about.meta' });
// ...
}
Rules:
- Call
setRequestLocale()before anygetMessages()orgetTranslations()call - Call it in every layout and page under
[locale]/, not just the layout - The locale value comes from
params.locale(the route segment)
Verification
- Run
npm run build— locale pages should still be listed with●(SSG) - Run
npm run devand navigate to a locale page (e.g.,/zh-CN) - Verify client component text renders in the correct locale
- Verify
useTranslations()returns translated strings, not default locale
Why This Is Misleading
- Build succeeds with no warnings about wrong locale
generateMetadataworks if it passes locale explicitly:getTranslations({ locale })— this bypasses the request context entirely- Only
getMessages()(no locale param) is affected, because it reads from request context - The
NextIntlClientProviderreceives English messages but is toldlocale="zh-CN", creating a mismatch where the provider has the right locale but wrong messages
Notes
setRequestLocaleis described by next-intl as a "stopgap" until Next.js natively supports accessing route parameters in all Server Components- This is especially important when NOT using
createMiddlewarefromnext-intl/middleware(e.g., when using custom Supabase auth middleware) - Even when using next-intl's middleware,
setRequestLocaleis still recommended for static rendering support - The function is synchronous — no
awaitneeded