i18n v Next.js 16 s next-intl: průvodce internacionalizací App Routeru (2026)
Průvodce i18n v Next.js 16 App Routeru s next-intl: detekce jazyka v proxy.ts, překlady v Server Components, hreflang, ICU MessageFormat a statická generace.
Internacionalizaci (i18n) v Next.js 16 App Routeru řešíte kombinací dynamického segmentu [locale], detekce jazyka v proxy.ts a knihovny next-intl, která překlady bezpečně propojí se Server Components i s klientskými hooky. Vestavěná konfigurace i18n z next.config.js, na kterou jste možná zvyklí z Pages Routeru, v App Routeru už není (a je to záměr, protože routovací strom se stal explicitní). V tomhle průvodci ukážu, jak i18n v Next.js 16 postavit tak, aby zvládl SEO, Cache Components i staticky generované routy pro desítky jazyků najednou. Upřímně, když jsem to poprvé stavěl, sázel jsem půlku odpolední na to, kde setRequestLocale vlastně patří.
Next.js 16 App Router nemá vestavěnou i18n konfiguraci – internacionalizace se řeší dynamickým segmentem app/[locale]/... a middleware v proxy.ts.
next-intl 4.x je aktuálně nejlépe udržovaná knihovna pro App Router a plně podporuje Server Components, Server Actions i Cache Components.
Detekci jazyka provádějte v proxy.ts podle hlavičky Accept-Language s fallbackem na cookie a výchozí lokál.
Pro statické generování všech jazykových variant použijte generateStaticParams nad segmentem [locale] – jinak vám ISR nafoukne serverovou zátěž.
SEO vyžaduje hreflang odkazy a lokalizované metadata skrz generateMetadata s alternates.languages.
Slovníkové soubory nikdy neposílejte celé do prohlížeče – next-intl je serializuje po namespaces a klient dostane jen to, co skutečně potřebuje.
Proč App Router nemá vestavěnou i18n konfiguraci
Když jsem v roce 2023 přepisoval firemní portál z Pages Routeru na App Router, první šok přišel u next.config.js: klíč i18n byl pryč a v dokumentaci stálo, ať si routování postavím sám. Tehdy to působilo jako regrese. Dneska, po několika letech práce s Server Components a Cache Components, vidím logiku: vestavěná i18n konfigurace v Pages Routeru byla těsně svázaná s výchozím _middleware a s vlastní logikou statického exportu. App Router zavedl explicitní segmenty a strom se stal jediným zdrojem pravdy o tom, které URL existují. Kdyby framework do stromu automaticky vkládal jazykový prefix, tak by rozbil generateStaticParams, Parallel Routes i Intercepting Routes.
Řešení je proto dvoudílné. Za prvé si vytvoříte adresář app/[locale]/ a všechny stránky přesunete pod něj. Segment [locale] se propíše do všech params objektů, dostane se do generateMetadata i do Cache Components. Za druhé si postavíte middleware – v Next.js 16 se soubor jmenuje proxy.ts a nahradil starý middleware.ts – který podle URL nebo hlaviček rozhodne, který jazyk uživateli servírovat. Framework vám dá primitiva; knihovny jako next-intl, next-international nebo @lingui/react vám je poskládají do použitelného API. Pro naprostou většinu projektů dnes doporučuji next-intl, protože jako jediná plně chápe React Server Components a nesnaží se je obejít "use client" obalováním.
Jak nastavit i18n v Next.js 16 od nuly
Ukážu si čistý start na nové aplikaci. Nainstalujte next-intl a připravte adresářovou strukturu:
Soubor i18n/routing.ts definuje kanonický seznam lokálů a výchozí jazyk. Tuhle konfiguraci sdílí middleware, komponenty i navigační helpery, takže se vyplatí ji mít na jednom místě:
Volba localePrefix: 'as-needed' nechá výchozí jazyk bez prefixu, což je dobré pro SEO domácího trhu, ale zároveň znamená, že si musíte hlídat kanonické URL. Alternativa 'always' je čistší, když provozujete více rovnocenných trhů. Do i18n/request.ts pak nakonfigurujte, odkud next-intl načte překlady na serveru:
Dynamický import() je klíč: Turbopack ho rozseká na chunky podle jazyků, takže build neroste lineárně s počtem překladů a Serverless runtime na Vercelu si stahuje jen aktivní locale. Do next.config.ts ještě přidejte plugin, který next-intl propojí s buildem:
Middleware v Next.js 16 běží na Edge Runtime a musí být rychlý, typicky pod 5 ms. next-intl nabízí hotový createMiddleware, který za vás vyřeší tři věci: přesměrování z / na správný lokál, rewrite URL bez prefixu na interní [locale] segment a nastavení Set-Cookie s uživatelovou volbou. Takže v praxi to vypadá takhle:
// proxy.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// Vyhněte se statickým assetům a API – middleware
// by je zbytečně zpomaloval a rozbil by /api/webhook trasy.
matcher: ['/((?!api|trpc|_next|_vercel|.*\\..*).*)'],
};
Pod kapotou middleware čte hlavičku Accept-Language, porovná ji s vaším seznamem lokálů pomocí algoritmu z RFC 4647 a vybere nejlepší shodu. Když má uživatel v prohlížeči cs-CZ,cs;q=0.9,en;q=0.8, dostane češtinu. Pokud je v cookie NEXT_LOCALE=de, cookie má přednost – to je důležité, jinak by uživatelé nemohli přepnout jazyk manuálně. Cookie se vytvoří automaticky, jakmile uživatel navštíví lokalizovanou URL.
Překlady v Server Components a klientských komponentách
V App Routeru máte dva světy: Server Components (výchozí) a klientské komponenty ("use client"). next-intl nabízí pro každý jinou cestu, ale API se úmyslně shoduje. Na serveru použijte asynchronní getTranslations:
// app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from 'next-intl/server';
export default async function HomePage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
// Aktivujeme statickou generaci – bez toho by se page.tsx
// vždy vygenerovala dynamicky, i když ji máte v generateStaticParams.
setRequestLocale(locale);
const t = await getTranslations('HomePage');
return (
<main>
<h1>{t('title')}</h1>
<p>{t('description', { name: 'Mei-Lin' })}</p>
</main>
);
}
Na klientovi obalte podstrom komponentou NextIntlClientProvider a použijte useTranslations. Provider vezme namespace, který skutečně potřebujete, a serializuje ho do HTML jako __NEXT_DATA__. Pro velké aplikace to znamená, že klient dostane jednotky kilobytů překladů místo celého slovníku:
Slovníky pište v ICU MessageFormat, který next-intl podporuje nativně. ICU pokryje plurály, gender i výběry – a v češtině to potřebujete, protože máme čtyři plurálové kategorie (one, few, many, other) a ne dvě jako angličtina:
{
"Cart": {
"items": "{count, plural, one {# položka} few {# položky} many {# položek} other {# položek}}"
}
}
Statické generování jazykových variant
Bez generateStaticParams se každá stránka v [locale] segmentu vygeneruje dynamicky na požádání. Pro marketingové stránky, blog nebo dokumentaci je to plýtvání – obsah se mění minutu za den a request-time renderování jen zdržuje. Generujte proto všechny lokály staticky:
// app/[locale]/layout.tsx
import { routing } from '@/i18n/routing';
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
Pokud máte lokalizované slugy (např. /cs/o-nas a /en/about-us), zkombinujte to s generateStaticParams u konkrétní stránky a použijte funkci pathnames z next-intl, která mapuje interní klíč na lokalizovanou URL. V praxi tak vývojářský router pracuje s klíčem /about, ale prohlížeč vidí /cs/o-nas. Kombinace s Cache Components a Partial Prerendering vám dovolí statický shell servírovat z CDN a dynamickou část (třeba přihlášeného uživatele) renderovat streamem – jazykový segment na to nemá vliv, dokud setRequestLocale zavoláte před prvním await pro data.
Lokalizovaná metadata a hreflang pro SEO
Bez správných hreflang odkazů vám Google bude v mezinárodních výsledcích ukazovat špatnou jazykovou verzi. V Next.js 16 se to řeší přes generateMetadata a klíč alternates.languages. Detailně jsem to rozebírala v samostatném článku o generateMetadata v Next.js 16, tady ukážu i18n specifickou část:
Hodnotu x-default v alternates.languages vyžaduje Google podle své dokumentace k lokalizovaným verzím jako fallback pro uživatele, kteří neodpovídají žádnému definovanému lokálu. Nezapomínejte ani na sitemap.ts: každou URL vypište pro všechny lokály a přidejte alternates pole. Vygenerovaný sitemap.xml pak Google pošle jasný signál o strukturách překladů.
Server Actions, formátování a časové zóny
Server Actions přijímají FormData a běží na serveru bez klientského kontextu, ale next-intl jim umí předat aktuální lokál. Kombinace vypadá takto – a v tomto vzoru mě dvakrát pálila validace, takže si dejte pozor:
Kompletní vzor formulářového flow, včetně useActionState a validace na klientu, najdete v našem článku o Server Actions v Next.js 16. Pro formátování dat, čísel a měn používejte useFormatter na klientu a getFormatter na serveru – oba vrací obálku nad Intl.DateTimeFormat a Intl.NumberFormat, které jsou v Node 22 i v Edge Runtime plně podporovány. Časovou zónu předávejte explicitně v i18n/request.ts přes timeZone: 'Europe/Prague', jinak dostanete UTC a uživatelé v CET uvidí čas o hodinu jinak.
Migrace z next-i18next a Pages Routeru
Pokud stěhujete z Pages Routeru, kde jste používali next-i18next nebo next-translate, čeká vás víc než přepis importů. Rozdíly mezi App Routerem a Pages Routerem shrnuje následující tabulka:
Vlastnost
Pages Router (next-i18next)
App Router (next-intl 4)
Konfigurace i18n
Klíč i18n v next.config.js
Vlastní [locale] segment a plugin
Detekce jazyka
Vestavěná v routeru
proxy.ts middleware
Server Components
Nepodporováno
Nativní podpora přes getTranslations
Cache Components
N/A
Kompatibilní s use cache
ICU MessageFormat
Přes plugin
Nativně
Bundle size klienta
Celý slovník na klient
Selektivně přes namespace
SSG/ISR
getStaticProps s locales
generateStaticParams nad [locale]
Runtime
Node.js
Node.js i Edge
Migrace typicky trvá 2 až 4 týdny pro projekt střední velikosti. Doporučuji tři fáze: (1) přesunout app/ pod app/[locale]/ a přesměrovat kořenový router, (2) přepsat všechny useTranslation importy na useTranslations a slovníky převést do JSON (většinou jq jednořádkovka), (3) nahradit vlastní middleware kodem od next-intl. Zpočátku můžete běžet paralelně – App Router a Pages Router v jedné aplikaci koexistují – a přehodit trafik postupně přes proxy.ts.
Častá úskalí v produkci
Za poslední dva roky jsem viděla stejné bugy dokola (u sebe i u kolegů). První: chybějící setRequestLocale ve stránce. Bez něj vám next-intl vyhlásí, že komponenta nemůže být staticky vygenerována, a přehodí ji do dynamic rendering. Sledujte to v build logu – Next.js 16 označí ovlivněné routy jako ƒ (Dynamic) místo ○ (Static). Druhé: kolize s revalidateTag. Když invalidujete cache pro tag: 'products', invaliduje se pro všechny lokály naráz. Buď použijte [tag, locale] složené tagy, nebo si přijměte, že revalidace je globální.
Třetí typický problém: chybějící překlady v produkci. V development modu next-intl hlásí chybějící klíč do konzole, ale v produkci defaultně vypíše samotný klíč jako fallback. Nastavte onError handler v NextIntlClientProvider a posílejte to do Sentry nebo do jiného trackeru – jinak vám v UI vyskočí Cart.items.plural místo textu a uživatel si toho všimne dřív než vy. Čtvrté: RTL jazyky. Pokud podporujete arabštinu nebo hebrejštinu, nastavte <html dir> podle lokálu (pomocná mapa v routing.ts) a v CSS používejte logické vlastnosti (margin-inline-start místo margin-left). Tailwind 4 to už umí nativně přes ms-* a me-* třídy.
A poslední: cache slovníků. Když nasadíte nový build, staré klienty mají v paměti staré překlady, ale nové URL. To způsobí desetiminutové období, kdy někteří uživatelé vidí undefined nebo hláškový klíč. Verzujte proto namespace nebo nastavte krátký Cache-Control pro HTML odpovědi – RSC payload se stejně obnoví při navigaci, ale zkuste to najít bez správného sledování. Kompletní přehled setup Next.js App Routeru v next-intl dokumentaci a Next.js návod na internationalization vám ukáží ještě víc hraniček, na které dřív či později narazíte.
Často kladené otázky
Podporuje Next.js 16 App Router i18n nativně?
Ne v tom smyslu, jak to znáte z Pages Routeru. Vestavěná konfigurace i18n v next.config.js v App Routeru neexistuje. Framework vám dá primitiva – dynamické segmenty a proxy.ts middleware – a knihovna jako next-intl nad tím postaví komplexní řešení.
Která knihovna je nejlepší pro internacionalizaci Next.js v roce 2026?
Pro App Router doporučuji next-intl 4.x. Nativně podporuje Server Components, Server Actions, Cache Components, ICU MessageFormat i statickou generaci. Alternativy jako next-international nebo @lingui/react jsou funkční, ale s Server Components mají hrubší integraci.
Jak nastavit hreflang v Next.js App Routeru?
Ve funkci generateMetadata nastavte alternates.languages jako objekt s páry [locale, url] a přidejte klíč x-default pro fallback. Next.js vygeneruje odpovídající <link rel="alternate" hreflang="..."> tagy do <head>.
Jak funguje detekce jazyka v Next.js middleware?
createMiddleware z next-intl čte hlavičku Accept-Language a porovnává ji se seznamem lokálů podle RFC 4647. Cookie NEXT_LOCALE má přednost před hlavičkou, aby uživatelé mohli přepnout jazyk manuálně. Fallbackem je výchozí lokál z konfigurace.
Můžu migrovat z next-i18next na next-intl postupně?
Ano. App Router a Pages Router v jedné Next.js aplikaci koexistují, takže můžete přesouvat rout po routě. Doporučuji začít marketingovými stránkami, které mají statický obsah, a nechat dashboardy nebo checkout na konec – ty mají typicky komplexnější formuláře a validaci.
Roste bundle size klienta lineárně s počtem překladů?
Ne, pokud používáte next-intl správně. Knihovna serializuje na klient jen ty namespaces, které komponenta v aktuálním stromě potřebuje. Slovníky pro neaktivní lokály se nikdy do klientského bundle nedostanou a Turbopack je rozseká na chunky podle jazyků.
Parallel Routes a Intercepting Routes v Next.js 16 umožňují vykreslit modální okna s deep linkingem. Praktický vzor, default.tsx fallback, soft vs. hard navigace a workaround pro HMR chybu v 16.0–16.2.
Kompletní průvodce migrací z middleware.ts na proxy.ts v Next.js 16 — vzory autentizace s Data Access Layer, integrace Auth.js v5 a obrana proti CVE-2025-29927.