The easiest way to translate your Next.js apps.
Supports the App Router (Server Components, Client Components, middleware), the Pages Router, and mixed setups where both routers coexist.
If you already know i18next: next-i18next v16 is a thin layer on top of i18next and react-i18next that handles the Next.js-specific wiring โ middleware, server/client split, resource hydration โ so you don't have to.
getT() for Server Components, useT() for Client Components, createProxy() for language detection and routing/en/about) and no-locale-path (cookie-based) modesbasePath scopingi18next-http-backend, i18next-locize-backend, i18next-chained-backend, etc.appWithTranslation / serverSideTranslations API preserved under next-i18next/pagesnpm install next-i18next i18next react-i18next
Place JSON translation files in your project. There are two common patterns:
In public/locales/ (served statically, works with default config โ local/traditional hosting only):
public/locales/en/common.json
public/locales/en/home.json
public/locales/de/common.json
public/locales/de/home.json
Serverless platforms (Vercel, AWS Lambda, etc.): Files in
public/are served via CDN but are not available on the filesystem at runtime. UseresourceLoaderwith dynamic imports instead (see below).
In app/i18n/locales/ (bundled via dynamic imports, requires resourceLoader โ works everywhere including serverless):
app/i18n/locales/en/common.json
app/i18n/locales/de/common.json
Create a config file (e.g., i18n.config.ts):
import type { I18nConfig } from 'next-i18next/proxy'
const i18nConfig: I18nConfig = {
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
defaultNS: 'common',
ns: ['common', 'home'],
// Recommended: works on all platforms including Vercel/serverless
resourceLoader: (language, namespace) =>
import(`./app/i18n/locales/${language}/${namespace}.json`),
}
export default i18nConfig
The resourceLoader uses dynamic import() which the bundler can trace, ensuring translation files are included in the serverless function bundle. If you prefer to keep translations in public/locales/ and are not deploying to a serverless platform, you can omit resourceLoader โ next-i18next will read from the filesystem at runtime.
Tip: Import
I18nConfigfromnext-i18next/proxy(not fromnext-i18next) to keep the config file Edge-safe.
Create proxy.ts at your project root (Next.js 16+ replaces middleware.ts with proxy.ts):
import { createProxy } from 'next-i18next/proxy'
import i18nConfig from './i18n.config'
export const proxy = createProxy(i18nConfig)
export const config = {
matcher: ['/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js|site.webmanifest).*)'],
}
Note:
createMiddlewarefromnext-i18next/middlewareis still available for projects on Next.js < 16.
The proxy:
/about -> /en/about)x-i18next-current-language) for Server Components// app/[lng]/layout.tsx
import { initServerI18next, getT, getResources, generateI18nStaticParams } from 'next-i18next/server'
import { I18nProvider } from 'next-i18next/client'
import i18nConfig from '../../i18n.config'
initServerI18next(i18nConfig)
export async function generateStaticParams() {
return generateI18nStaticParams()
}
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ lng: string }>
}) {
const { lng } = await params
const { i18n } = await getT()
const resources = getResources(i18n)
return (
<html lang={lng}>
<body>
<I18nProvider language={lng} resources={resources}>
{children}
</I18nProvider>
</body>
</html>
)
}
Key points:
initServerI18next(config) โ call once at module scope in the root layoutgetResources(i18n) โ serializes loaded translations for client hydrationI18nProvider โ wraps children so client components can use useT()// app/[lng]/page.tsx
import { getT } from 'next-i18next/server'
export default async function Home() {
const { t } = await getT('home')
return <h1>{t('title')}</h1>
}
export async function generateMetadata() {
const { t } = await getT('home')
return { title: t('meta_title') }
}
For the Trans component in Server Components, use react-i18next/TransWithoutContext and pass both t and i18n:
import { Trans } from 'react-i18next/TransWithoutContext'
import { getT } from 'next-i18next/server'
export default async function Page() {
const { t, i18n } = await getT()
return (
<Trans t={t} i18n={i18n} i18nKey="welcome">
Welcome to <strong>next-i18next</strong>
</Trans>
)
}
'use client'
import { useT } from 'next-i18next/client'
export default function Counter() {
const { t } = useT('home')
return <button>{t('click_me')}</button>
}
useT works in both locale-in-path (/en/about) and no-locale-path modes. It accepts [lng] or [locale] as the dynamic route param name.
For the Trans component in Client Components:
'use client'
import { Trans, useT } from 'next-i18next/client'
export default function Greeting() {
const { t } = useT()
return <Trans t={t} i18nKey="greeting">Hello <strong>world</strong></Trans>
}
When the locale is part of the URL path (e.g., /en/about โ /de/about), switch languages by navigating to the new locale prefix:
'use client'
import { usePathname, useRouter } from 'next/navigation'
export function LanguageSwitcher({ supportedLngs }: { supportedLngs: string[] }) {
const pathname = usePathname()
const router = useRouter()
const switchLocale = (locale: string) => {
const segments = pathname.split('/')
segments[1] = locale
router.push(segments.join('/'))
}
return (
<div>
{supportedLngs.map((lng) => (
<button key={lng} onClick={() => switchLocale(lng)}>{lng}</button>
))}
</div>
)
}
For the no-locale-path mode (cookie-based), see useChangeLanguage below.
If you want clean URLs for the default language while keeping locale prefixes for other languages, set hideDefaultLocale: true:
const i18nConfig: I18nConfig = {
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
hideDefaultLocale: true,
}
In this mode:
/about serves the default language (English) โ no prefix needed/de/about serves German โ non-default locales keep their prefix/en/about automatically redirects to /about (canonical clean URL)[lng] folder structure stays the same โ the proxy rewrites internallyIf you prefer clean URLs without a locale prefix for all languages (e.g., /about instead of /en/about), set localeInPath: false:
const i18nConfig: I18nConfig = {
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
localeInPath: false,
resourceLoader: (language, namespace) =>
import(`./app/i18n/locales/${language}/${namespace}.json`),
}
In this mode:
app/ (no [lng] segment)getT() as usual (language is read from the header)useT() as usual (language comes from I18nProvider)useChangeLanguage() for language switching (updates cookie + triggers server re-render):'use client'
import { useChangeLanguage } from 'next-i18next/client'
export function LanguageSwitcher() {
const changeLanguage = useChangeLanguage()
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('de')}>Deutsch</button>
</div>
)
}
The root layout reads the language from i18n.resolvedLanguage instead of URL params:
// app/layout.tsx (no [lng] segment)
export default async function RootLayout({ children }) {
const { i18n } = await getT()
const lng = i18n.resolvedLanguage
const resources = getResources(i18n)
return (
<I18nProvider language={lng} resources={resources}>
<html lang={lng}>
<body>{children}</body>
</html>
</I18nProvider>
)
}
See examples/app-router-no-locale-path for a complete example.
For projects that use both routers, next-i18next supports a basePath option that scopes the App Router middleware to a specific URL prefix while the Pages Router uses Next.js built-in i18n routing for everything else.
Create a shared config file for common settings:
// i18n.shared.js
module.exports = {
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
defaultNS: 'common',
ns: ['common', 'footer'],
}
App Router config with basePath:
// i18n.config.ts
import type { I18nConfig } from 'next-i18next/proxy'
const shared = require('./i18n.shared.js')
const i18nConfig: I18nConfig = {
...shared,
basePath: '/app-router',
resourceLoader: (language, namespace) =>
import(`./public/locales/${language}/${namespace}.json`),
}
export default i18nConfig
Pages Router config:
// next-i18next.config.js
const shared = require('./i18n.shared.js')
module.exports = {
i18n: {
defaultLocale: shared.fallbackLng,
locales: shared.supportedLngs,
},
localePath:
typeof window === 'undefined'
? require('path').resolve('./public/locales')
: '/locales',
}
With basePath: '/app-router', createProxy automatically:
/app-router/... (letting Pages Router handle those)/app-router/page to /app-router/en/page// proxy.ts
import { createProxy } from 'next-i18next/proxy'
import i18nConfig from './i18n.config'
export const proxy = createProxy(i18nConfig)
export const config = {
matcher: ['/app-router/:path*'],
}
Include the Pages Router i18n config so Next.js handles locale routing for Pages:
const { i18n } = require('./next-i18next.config.js')
module.exports = {
i18n,
reactStrictMode: true,
}
app/app-router/[locale]/layout.tsx -- App Router layout
app/app-router/[locale]/page.tsx -- App Router pages
pages/_app.tsx -- appWithTranslation
pages/index.tsx -- Pages Router pages
public/locales/en/common.json -- shared translation files
App Router pages import from next-i18next/server and next-i18next/client.
Pages Router pages import from next-i18next/pages and next-i18next/pages/serverSideTranslations.
See examples/mixed-routers for a complete example.
For projects using only the Pages Router, the familiar v15 API is available under next-i18next/pages:
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
}
// next.config.js
const { i18n } = require('./next-i18next.config.js')
module.exports = { i18n }
// pages/_app.tsx
import { appWithTranslation } from 'next-i18next/pages'
const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />
export default appWithTranslation(MyApp)
// pages/index.tsx
import { serverSideTranslations } from 'next-i18next/pages/serverSideTranslations'
import { useTranslation } from 'next-i18next/pages'
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, ['common'])),
},
})
export default function Home() {
const { t } = useTranslation('common')
return <h1>{t('title')}</h1>
}
See examples/pages-router-simple, examples/pages-router-ssg, and examples/pages-router-auto-static-optimize for more.
next-i18next supports any i18next backend plugin for loading translations from an API, CDN, or services like Locize.
When a custom backend is provided via use, next-i18next will not add its default resource loader, giving you full control.
import { defineConfig } from 'next-i18next'
import HttpBackend from 'i18next-http-backend'
export default defineConfig({
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
use: [HttpBackend],
i18nextOptions: {
backend: {
loadPath: 'https://cdn.example.com/locales/{{lng}}/{{ns}}.json',
},
},
})
import { defineConfig } from 'next-i18next'
import LocizeBackend from 'i18next-locize-backend'
export default defineConfig({
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
use: [LocizeBackend],
i18nextOptions: {
backend: {
projectId: 'your-project-id',
apiKey: 'your-api-key', // only needed for saving missing keys
},
},
})
For client-side caching with a remote fallback:
import { defineConfig } from 'next-i18next'
import ChainedBackend from 'i18next-chained-backend'
import HttpBackend from 'i18next-http-backend'
import LocalStorageBackend from 'i18next-localstorage-backend'
export default defineConfig({
supportedLngs: ['en', 'de'],
fallbackLng: 'en',
use: [ChainedBackend],
i18nextOptions: {
backend: {
backends: [LocalStorageBackend, HttpBackend],
backendOptions: [
{ expirationTime: 7 * 24 * 60 * 60 * 1000 },
{ loadPath: '/locales/{{lng}}/{{ns}}.json' },
],
},
},
})
For the client-side I18nProvider, pass custom backend plugins via the use prop:
<I18nProvider
language={lng}
resources={resources}
use={[HttpBackend]}
i18nextOptions={{
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
}}
>
{children}
</I18nProvider>
On the server, next-i18next uses a module-level singleton i18next instance:
In serverless environments (Lambda, Vercel Serverless, etc.), the cache only lives as long as the warm function instance. For serverless, prefer:
resources or resourceLoader with dynamic importsnext-i18next (root export)| Export | Description |
|---|---|
defineConfig(config) | Type-safe config helper (identity function) |
normalizeConfig(config) | Fill in defaults and validate config |
I18nConfig | Config type |
next-i18next/proxy (also available as next-i18next/middleware)| Export | Description |
|---|---|
createProxy(config) | Returns a Next.js proxy function (for proxy.ts) |
createMiddleware(config) | Alias for createProxy (for middleware.ts on Next.js < 16) |
defineConfig, normalizeConfig, I18nConfig | Re-exported for Edge-safe config usage |
next-i18next/server| Export | Description |
|---|---|
initServerI18next(config) | Initialize server config (call once at module scope) |
getT(ns?, options?) | Get { t, i18n } for Server Components. Options: { lng?, keyPrefix? } |
getResources(i18n, namespaces?) | Extract loaded resources for client hydration |
generateI18nStaticParams() | Returns [{ lng: 'en' }, { lng: 'de' }, ...] for generateStaticParams |
next-i18next/client| Export | Description |
|---|---|
I18nProvider | Client-side provider wrapping I18nextProvider |
useT(ns?, options?) | Translation hook for Client Components (works in all modes) |
useChangeLanguage(cookieName?) | Language switcher hook for no-locale-path mode |
Trans | Re-exported from react-i18next |
next-i18next/pages| Export | Description |
|---|---|
appWithTranslation | HOC for _app |
useTranslation | Translation hook (re-exported from react-i18next) |
Trans, I18nContext, withTranslation | Re-exported from react-i18next |
next-i18next/pages/serverSideTranslations| Export | Description |
|---|---|
serverSideTranslations(locale, ns?, config?, extraLocales?) | Load translations for getStaticProps / getServerSideProps |
| Option | Default | Description |
|---|---|---|
supportedLngs | required | Array of supported language codes |
fallbackLng | required | Default language |
defaultNS | 'common' | Default namespace |
ns | [defaultNS] | All known namespaces |
localeInPath | true | Include locale in URL path |
hideDefaultLocale | false | When true (with localeInPath: true), the default language has no URL prefix |
localePath | '/locales' | Path to locale files relative to /public |
localeStructure | '{{lng}}/{{ns}}' | Locale file directory structure |
localeExtension | 'json' | Locale file extension |
resources | โ | Pre-loaded resources (skips dynamic loading) |
resourceLoader | โ | Custom async loader (lng, ns) => Promise<object> |
basePath | โ | URL prefix for proxy/middleware scoping (e.g., '/app-router') |
cookieName | 'i18next' | Cookie name for language persistence |
headerName | 'x-i18next-current-language' | Header name for server-side language passing |
cookieMaxAge | 31536000 (1 year) | Cookie max age in seconds |
ignoredPaths | ['/api', '/_next', '/static'] | Paths the proxy/middleware should skip |
use | [] | Extra i18next plugins |
i18nextOptions | {} | Additional i18next init options |
nonExplicitSupportedLngs | false | Match 'en' to 'en-US' etc. |
| Example | Description |
|---|---|
app-router-simple | App Router with locale-in-path (/en/...) |
app-router-no-locale-path | App Router with cookie-based language (no locale in URL) |
mixed-routers | App Router + Pages Router in the same project |
pages-router-simple | Pages Router with getStaticProps / getServerSideProps |
pages-router-ssg | Pages Router with static export (output: 'export') |
pages-router-auto-static-optimize | Pages Router with client-side loading via chained backend |
If you were using i18next and react-i18next directly (as recommended in v15):
npm install next-i18next@16i18n.config.ts with your languages and namespacescreateProxy(config) in proxy.tsgetT / translation init with initServerI18next + getT from next-i18next/serverI18nProvider with the one from next-i18next/clientuseTranslation with useT from next-i18next/client in client componentsnext-i18next to next-i18next/pagesserverSideTranslations import to next-i18next/pages/serverSideTranslationsimport type { TFunction, WithTranslation, I18n } from 'next-i18next/pages'Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
localization as a service - Locize
Needing a translation management? Want to edit your translations with an InContext Editor? Use the original provided to you by the maintainers of i18next!
Now with a Free plan for small projects! Perfect for hobbyists or getting started.

By using Locize you directly support the future of i18next and next-i18next.