next-intl review
next-intl 4.13.7 is a Next.js-specific internationalization layer for ICU messages, locale-aware routing, number and date formatting, Server Components, and client hooks. Its plugin connects request configuration to Next.js, while separate server, navigation, routing, and middleware exports keep code on the correct side of the React boundary. The current patch pins @swc/core to the extractor-compatible range. Version 4.13 also changed generated useExtracted keys to URL-safe base64, which requires migrating existing extracted catalogs.
next-intl 4.13.7 installed with 51 packages and produced a 15.8 KB gzipped broad browser bundle in our sandbox, while npm audit found 0 vulnerabilities. It is a strong fit when Next.js routing and Server Components are part of the translation problem; use a framework-neutral library when they are not.
We installed it
| Install | ✓ · 13.7s | 51 packages on disk · 374 MB |
| Import | ✓ | ESM import works · require() works · ESM package with exports map |
| Browser | 15.8 KB | gzipped (50.3 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does next-intl install cleanly?
Yes. In a fresh container with an empty cache, npm install next-intl finished in 14 seconds, leaving 51 packages and 374 MB on disk. npm audit reported no known vulnerabilities.
How much does next-intl add to a browser bundle?
15.8 KB gzipped (50.3 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does next-intl work with both ESM and CommonJS?
Yes. Both import 'next-intl' and require('next-intl') worked in Node 22 in our run. The package is published as ESM with an exports map.
Does next-intl include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
next-intl or next-i18next: which should you use?
next-i18next: Choose it for a Next.js codebase already organized around i18next resources, plugins, and backend loaders. next-intl 4.13.7 installed with 51 packages and produced a 15.8 KB gzipped broad browser bundle in our sandbox, while npm audit found 0 vulnerabilities.
When should you not use next-intl?
You are not using Next.js; i18next, react-intl, or the lower-level use-intl package avoids framework-specific plugin and routing code
Use it if
- Your Next.js App Router project needs ICU plurals, rich text messages, formatting, and localized routes under one API
- You want translations to work in Server Components and Client Components without maintaining two unrelated libraries
- Compile-time checks for locale names and message keys justify typing a canonical message catalog
- Your routing policy needs locale prefixes, translated pathnames, cookies, negotiation, and locale-aware Link wrappers
- You are not using Next.js; i18next, react-intl, or the lower-level use-intl package avoids framework-specific plugin and routing code
- A 15.8 KB gzipped browser bundle is too much for the translated client surface; our broad package import measured 50.3 KB minified
- You want translations managed entirely by a hosted service; next-intl formats and loads catalogs but does not supply translators or a content workflow
- You cannot maintain request config, locale routing, message files, provider boundaries, and Next.js middleware or proxy behavior together
- You depend heavily on experimental useExtracted output and cannot absorb key migrations; 4.13 switched generated keys to URL-safe base64
Setup reality
Our next-intl 4.13.7 install took 13.7 seconds and left 51 packages occupying 374 MB. npm audit reported 0 known vulnerabilities. The package has 8 direct dependencies, 2 peer dependencies, and 1,668 KB unpacked. Those peers are Next.js and React, so the sandbox total reflects a framework install rather than next-intl alone.
Create an i18n request file that returns locale, messages, and optional formats, then wrap next.config with next-intl/plugin. Locale routing adds a shared defineRouting object, generated navigation wrappers, and middleware or proxy configuration. Next.js 16 uses proxy.ts. Older releases use middleware.ts. Message catalogs, fallbacks, and translation delivery remain application responsibilities.
The package is ESM with an exports map and bundled declarations. Both require() and ESM import worked on our Node 22 box. Conditional exports provide different client and React Server Component entry points, so importing from next-intl/server or next-intl/navigation is part of correctness. Sending every message through NextIntlClientProvider exposes that catalog to the client; pass only the namespaces client components use when payload size matters.
Our broad esbuild import produced 50.3 KB minified and 15.8 KB gzipped. Actual route cost depends on imports and server placement, but client hooks and message data still enter the browser. Release 4.13.7 pins @swc/core for the extractor plugin. If useExtracted was already generating keys before 4.13, migrate the stored keys before upgrading because the generated encoding changed.
Patterns
Load a catalog for each request configure-request
// src/i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async ({locale}) => ({
locale: locale ?? 'en',
messages: (await import(`../../messages/${locale ?? 'en'}.json`)).default
}));Version 4.13.6 deprecated the requestLocale parameter; follow the current request configuration docs when moving to Next.js root params.
Attach next-intl to Next.js enable-plugin
// next.config.ts
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
export default withNextIntl({});The plugin searches for the conventional i18n request file; pass its relative path when your file lives elsewhere.
Translate in an async Server Component translate-server-component
import {getTranslations} from 'next-intl/server';
export default async function Page() {
const t = await getTranslations('Home');
return <h1>{t('title')}</h1>;
}The server export keeps translation work out of the client bundle and is the correct API after an async boundary.
Provide selected messages to client code provide-client-catalog
import {NextIntlClientProvider} from 'next-intl';
export function ClientBoundary({children, messages}) {
return <NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>;
}Every message passed to the provider can reach the browser; select the client namespaces instead of forwarding a full catalog.
Render an ICU plural format-plural
// en.json: {"Cart":{"items":"{count, plural, =0 {Empty} one {# item} other {# items}}"}}
const t = useTranslations('Cart');
return <p>{t('items', {count})}</p>;The one and other branches follow the active locale's plural rules, while =0 is an exact numeric match.
Map a message tag to React render-rich-text
const t = useTranslations('Help');
return t.rich('docs', {
link: (chunks) => <a href='/docs'>{chunks}</a>
});Use t.rich for React elements; a plain t call expects a string and t.raw skips ICU processing.
Define locale routing once define-routing
// src/i18n/routing.ts
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en', 'de'],
defaultLocale: 'en',
localePrefix: 'as-needed'
});Share this 1 routing object with navigation and proxy code so generated links and request rewrites use the same locale policy.
Negotiate locale routes in Next.js 16 configure-proxy
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {matcher: '/((?!api|_next|.*\..*).*)'};Next.js 16 names this file proxy.ts; the dotted-path exclusion also skips legitimate route segments containing a period unless you add a matcher.
Create locale-aware navigation helpers create-navigation
// src/i18n/navigation.ts
import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
export const {Link, redirect, usePathname, useRouter} = createNavigation(routing);Use these wrappers where links and redirects must add, remove, or switch the locale prefix according to routing policy.
Type locale and message names type-message-keys
import {routing} from '@/i18n/routing';
import messages from './messages/en.json';
declare module 'next-intl' {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
}
}One canonical catalog drives key completion; a very large JSON type can increase TypeScript and editor work.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| next-i18next | npm | Choose it for a Next.js codebase already organized around i18next resources, plugins, and backend loaders. |
| i18next | npm | Choose it when translations must span several JavaScript frameworks or need its broad plugin and backend ecosystem. |
| react-intl | npm | Choose it for FormatJS-style ICU formatting in React when localized Next.js routing is outside the library's job. |
More web frontend guides
postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

