next-intl 4
This skill covers next-intl 4.x as documented on https://next-intl.dev/docs/getting-started (docs as of 4.14.9). It holds what differs from habit: surprising defaults, pitfalls, legacy APIs and the patterns the docs recommend. Each claim in the reference files ends with the page it comes from.
How to navigate
- Read the quick start and quick rules below. They catch the most frequent mistakes.
- Open the one reference file that matches the task, from the table at the end.
- When code uses
setRequestLocale,requestLocaleormiddleware.ts, read the "Old patterns" table first.
Quick start (App Router)
The App Router is the recommended path. The Pages Router stays supported, see references/setup.md. https://next-intl.dev/docs/getting-started/pages-router
// next.config.ts
import {NextConfig} from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
https://next-intl.dev/docs/getting-started/app-router
Without locale-based URLs, src/i18n/request.ts resolves the locale from a user preference, a cookie at its simplest:
// src/i18n/request.ts
import {cookies} from 'next/headers';
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async () => {
const store = await cookies();
const locale = store.get('locale')?.value || 'en';
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});
https://next-intl.dev/docs/getting-started/app-router
// src/app/layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {getLocale} from 'next-intl/server';
export default async function RootLayout({children}) {
const locale = await getLocale();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
}
https://next-intl.dev/docs/getting-started/app-router , https://next-intl.dev/docs/usage/configuration
import {useTranslations} from 'next-intl';
import {getTranslations} from 'next-intl/server';
export function HomePage() {
const t = useTranslations('HomePage'); // non-async component
return <h1>{t('title')}</h1>;
}
export async function Profile() {
const t = await getTranslations('ProfilePage'); // async component
return <h1>{t('title')}</h1>;
}
https://next-intl.dev/docs/getting-started/app-router
For /en/about style URLs, add src/i18n/routing.ts, src/proxy.ts, src/i18n/navigation.ts and a top-level [locale] segment: references/routing.md. https://next-intl.dev/docs/routing/setup
Quick rules
Setup
- The plugin finds
i18n/request.tsinsrc/or the project root (.ts,.tsx,.js,.jsx). Elsewhere, pass the path:createNextIntlPlugin('./somewhere/else/request.ts'). https://next-intl.dev/docs/getting-started/app-router - The docs example hardcodes
const locale = 'en'. Replace it with real resolution (cookie, user setting ornext/root-params). https://next-intl.dev/docs/getting-started/app-router getRequestConfigruns once per request, in the Server Components render pass, socookies()andheaders()work there. https://next-intl.dev/docs/usage/configuration- Set
timeZonein the request config. The default is the time zone of the server runtime. https://next-intl.dev/docs/usage/configuration
Routing
- Name the file
src/proxy.tson Next.js 16. The import stayscreateMiddlewarefromnext-intl/middleware. https://next-intl.dev/docs/routing/setup - Read the locale with
await rootParams.locale()fromnext/root-params, validate it withhasLocale(routing.locales, value), elsenotFound(). Next.js before 16.3 needsexperimental.rootParams. https://next-intl.dev/docs/routing/setup - Import
Link,redirect,usePathname,useRouterandgetPathnamefrom youri18n/navigation.tsinstead ofnext/linkornext/navigation. https://next-intl.dev/docs/routing/navigation - Switch locales with
router.replace(pathname, {locale})from that module.<Link locale>is not prefetched, to avoid overwriting the locale cookie. https://next-intl.dev/docs/routing/navigation redirectalways takes a locale:redirect({href: '/login', locale}). Writereturn redirect(...)so TypeScript narrows. https://next-intl.dev/docs/routing/navigation- The default matcher skips paths with a dot. Add an explicit entry for routes like
/users/jane.doe. https://next-intl.dev/docs/routing/middleware
Client Components
NextIntlClientProviderwith nomessagesprop sends ALL messages to the client. Passmessages={pick(messages, 'Namespace')}ormessages={null}to limit them. https://next-intl.dev/docs/environments/server-client-components- Prefer translating in a Server Component and passing labels as props or
childrento the Client Component. https://next-intl.dev/docs/environments/server-client-components onErrorandgetMessageFallbackcannot be passed from a Server Component. Set them in your own'use client'provider nested inside the server one, which keeps inheriting locale and messages, so do not pass those by hand. https://next-intl.dev/docs/usage/configuration
Messages
- Keys and namespaces cannot contain
., which means nesting. Use nested JSON only. https://next-intl.dev/docs/usage/translations - ICU argument names and
selectvalues allow letters, digits and_, no dashes: mapen-GBtoen_GB.selectneeds anothercase. https://next-intl.dev/docs/usage/translations - Self-closing tags need a closing tag in messages:
<br></br>. https://next-intl.dev/docs/usage/translations - Use
getTranslations({locale, namespace})in Server Actions, metadata and Route Handlers, where the locale may not be inferred. https://next-intl.dev/docs/environments/actions-metadata-route-handlers
Tests
- Vitest with
createNavigation:server.deps.inline: ['next-intl']. Jest:transformIgnorePatterns: ['node_modules/(?!next-intl)/']. next-intl is ESM-only. https://next-intl.dev/docs/environments/testing
Old patterns
| Old | Current |
|---|---|
src/middleware.ts | src/proxy.ts on Next.js 16, same createMiddleware code |
setRequestLocale(locale) at the top of every layout and page | next/root-params in i18n/request.ts, static rendering works without it |
getRequestConfig(async ({requestLocale}) => ...) | getRequestConfig(async ({locale}) => ...) with a rootParams.locale() fallback |
Flat message keys "HomePage.title" | Nested objects {"HomePage": {"title": ...}} |
t.raw HTML in dangerouslySetInnerHTML | t.rich (React) or t.markup (strings), t.raw only to skip parsing, sanitized |
Details: references/routing.md and references/messages.md.
Reference files
| File | Read it for |
|---|---|
| references/setup.md | Plugin, request config, provider defaults, locale without routing, message storage, Pages Router |
| references/routing.md | defineRouting, [locale] segment, proxy.ts, matcher, next/root-params, static rendering, localePrefix, domains, cookie, alternate links, localized pathnames, static export, troubleshooting |
| references/navigation.md | createNavigation, Link, useRouter, usePathname, redirect, getPathname, locale switcher, typed href |
| references/messages.md | useTranslations, namespaces, ICU plurals and select, rich text, t.markup, t.raw, t.has, arrays, missing messages, onError, time zone, now |
| references/formatting.md | useFormatter: dates, relative time, ranges, numbers, lists, display names, ICU skeletons, global formats |
| references/server-and-client.md | Server vs Client Components, picking messages, metadata, Server Actions, Zod, Route Handlers, sitemap, OG images, 404 and error files |
| references/extraction.md | Experimental useExtracted and getExtracted, plugin experimental options, PO files, precompile, custom codecs, monorepos |
| references/typescript-and-tooling.md | AppConfig augmentation, type-safe arguments, testing with Jest and Vitest, Storybook, use-intl core, runtime requirements, linting, translation management, agent conventions |
Crawled pages: SOURCES.md.