# Što ćete izgraditi#
Ovaj vodič prikazuje produkcijski spreman pristup za Next.js i18n App Router s URL-ovima s prefiksom jezika, detekcijom jezika, SEO-sigurnim metapodacima i skalabilnim workflowima prevođenja. Napisano je za timove koji isporučuju stvarne proizvode, gdje su cacheiranje, dinamičke rute i operacije nad sadržajem važnije od hello-world demo primjera.
Naučit ćete kako strukturirati rute poput /en/blog/... i /hr/blog/..., kako detektirati i zapamtiti jezik te kako generirati ispravne canonical i hreflang URL-ove za Google.
Također ćete vidjeti tri opcije workflowa prevođenja — od JSON datoteka do headless CMS-a i lokalizacijskih platformi — s kriterijima odlučivanja i tipičnim zamkama.
# Preduvjeti i preporučeni stack#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Next.js | 14.2 ili noviji | Stabilni obrasci App Routera, metadata API, middleware |
| Node.js | 18 ili noviji | Preporučen LTS |
| Hosting | Vercel ili ekvivalent | CDN cacheiranje čini i18n bugove vidljivijima |
| i18n biblioteka | preporučen next-intl | Nije obavezno, ali smanjuje količinu custom “plumbinga” |
| Izvor sadržaja | JSON, CMS ili lokalizacijski servis | Odaberite prema volumenu sadržaja i veličini tima |
Ako vam je SEO prioritet, prvo pročitajte ova dva vodiča kako biste uskladili očekivanja i izbjegli česte greške: Zašto Next.js za SEO i SEO za developere.
# Lokalizirano rutiranje u App Routeru#
App Router ne koristi stari i18n config iz Pages Routera za automatsko generiranje locale ruta. U praksi, lokalizaciju implementirate pomoću top-level locale segmenta.
Najodrživiji obrazac je:
/en/...za engleski/hr/...za hrvatski- opcionalno
/de/...itd.
Time dobivate:
- jasno razdvajanje za cacheiranje i indeksiranje
- stabilne URL-ove koje je lako dijeliti
- jednostavno generiranje
hreflangoznaka
Struktura foldera#
Tipična struktura:
app/[locale]/layout.tsxza layout svjestan localeaapp/[locale]/page.tsxza početnu stranicu po localeuapp/[locale]/blog/[slug]/page.tsxza lokalizirane blog postovemiddleware.tsza preusmjeravanje korisnika s/na najbolji locale
Ovo se također skalira na ugniježđene segmente poput app/[locale]/(marketing)/... i app/[locale]/(app)/... bez miješanja jezika.
Definiranje podržanih localeova#
Napravite jedan “single source of truth” u src/i18n/config.ts:
export const locales = ["en", "hr"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "en";
export function isLocale(value: string): value is Locale {
return (locales as readonly string[]).includes(value);
}To smanjuje greške gdje jedan dio aplikacije misli da podržavate en-GB, a drugi da je to samo en.
Layout svjestan localea#
U app/[locale]/layout.tsx validirajte locale što ranije. Ako nije podržan, vratite 404 kako biste izbjegli indeksiranje “smeće” URL-ova.
import { notFound } from "next/navigation";
import type { ReactNode } from "react";
import { isLocale, type Locale } from "@/i18n/config";
export default function LocaleLayout(props: {
children: ReactNode;
params: Promise<{ locale: string }>;
}) {
return props.params.then(({ locale }) => {
if (!isLocale(locale)) notFound();
return props.children;
});
}Time osiguravate da iz perspektive routera postoje samo /en i /hr.
🎯 Ključna poruka: Rute s prefiksom jezika najjednostavniji su način da caching, SEO i analitiku držite urednima, jer je locale dio URL-a, a ne nešto što se zaključuje iz headera.
# Detekcija jezika: redirecti, cookieji i UX#
Detekcija jezika je mjesto gdje mnoge i18n implementacije razbiju SEO i caching. Cilj je:
- korisnici koji dođu na
/trebaju završiti na pravom localeu - tražilice trebaju moći deterministički crawlatI svaki locale URL
- povratni korisnici trebaju ostati na odabranom localeu
Redoslijed prioriteta detekcije#
Praktičan redoslijed:
- 1Eksplicitni locale u URL-u, poput
/hr/...uvijek ima prednost - 2Locale cookie nakon ručnog odabira korisnika
- 3
Accept-Languageheader za posjetitelje prvi put - 4Fallback na default locale
To odgovara korisničkoj namjeri i izbjegava redirect loopove.
Middleware za preusmjeravanje s / na locale#
Dodajte middleware.ts i preusmjeravajte samo kada nema locale prefiksa. Izbjegavajte preusmjeravanje URL-ova koji već imaju locale prefiks, jer to stvara nepotrebne “hopove” i može zbuniti crawlere.
import { NextRequest, NextResponse } from "next/server";
import { defaultLocale, isLocale, locales } from "@/i18n/config";
function getPreferredLocale(req: NextRequest) {
const cookieLocale = req.cookies.get("locale")?.value;
if (cookieLocale && isLocale(cookieLocale)) return cookieLocale;
const header = req.headers.get("accept-language") || "";
const first = header.split(",")[0]?.trim().slice(0, 2);
if (first && isLocale(first)) return first;
return defaultLocale;
}
export function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
const pathLocale = pathname.split("/")[1];
if (pathLocale && isLocale(pathLocale)) return NextResponse.next();
if (pathname === "/" || pathname === "") {
const locale = getPreferredLocale(req);
const url = req.nextUrl.clone();
url.pathname = `/${locale}`;
return NextResponse.redirect(url);
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next|api|.*\\..*).*)"],
};Pohrana korisničkog odabira#
Kad korisnik promijeni jezik, eksplicitno postavite cookie. To sprječava “skakanje” natrag i naprijed na temelju browser headera.
Primjer API rute ili server actiona koja postavi locale=hr na 1 godinu je dovoljan. Ako ne želite cookieje, držite prebacivanje jezika isključivo URL-based, ali očekujte nešto lošiji UX kod ponovnih posjeta na /.
⚠️ Upozorenje: Nemojte mijenjati HTML sadržaj prema
Accept-Languagena istom URL-u. CDN-ovi najčešće cacheiraju po URL-u, a ne po headeru, pa možete poslužiti pogrešan jezik sljedećem korisniku i zbuniti Googleovo indeksiranje.
# Dinamički segmenti: slugovi, ID-evi i prevedeni URL-ovi#
Dinamički segmenti su mjesto gdje se i18n susreće sa strategijom sadržaja. Imate tri česte opcije:
| Strategija | Primjer URL-a | Prednosti | Nedostaci |
|---|---|---|---|
| Stabilan slug kroz localeove | /en/blog/nextjs-i18n i /hr/blog/nextjs-i18n | Jednostavno mapiranje, manje redirecta | Nije idealno za SEO na lokalnom jeziku |
| Potpuno prevedeni slugovi | /en/blog/nextjs-i18n i /hr/blog/nextjs-i18n-app-router | Najbolji lokalni SEO, bolji CTR | Zahtijeva mapiranje slugova po localeu |
| Rutiranje po ID-u | /en/blog/12345 | Nema sudara slugova, jednostavno s CMS-om | Najlošije za SEO i UX osim ako se ne upari sa slugom |
Za marketing stranice i blog postove, prevedeni slugovi obično pobjeđuju ako možete održavati mapiranje. Za user-generated content, stabilni slugovi ili ID-evi su sigurniji.
Implementacija lokaliziranih blog ruta#
Čest obrazac je dohvat sadržaja prema localeu plus slugu:
locale = en,slug = nextjs-i18n-app-routerlocale = hr,slug = nextjs-i18n-app-router
Ili ako su slugovi prevedeni:
locale = hr,slug = next-js-i18n-aplikacijski-router
Vaš model podataka treba jasan jedinstveni ključ. U CMS setupovima to je tipično ID dokumenta s lokaliziranim poljima.
Generiranje static params po localeu#
Ako statički generirate blog stranice, pobrinite se da generirate parametre za svaki locale.
import { locales } from "@/i18n/config";
export async function generateStaticParams() {
const posts = await fetch("https://example.com/api/posts").then((r) => r.json());
return locales.flatMap((locale) =>
posts
.filter((p: any) => p.locales.includes(locale))
.map((p: any) => ({ locale, slug: p.slug[locale] || p.slug.default }))
);
}Time izbjegavate klasičan bug gdje se engleske stranice buildaju, ali drugi localeovi u produkciji vraćaju 404.
# Metapodaci i SEO: canonicals, hreflang i signali indeksiranja#
SEO za i18n je većinom stvar jasnoće. Tražilicama treba:
- jedan canonical po stranici/localeu
- alternate jezične linkove preko
hreflang - dosljedno interno linkanje unutar svakog localea
- bez slučajnih duplikata nastalih zbog parametara, cookieja ili varijacija po headerima
Ako želite širi SEO kontekst na jednom mjestu, koristite SEO za developere uz ovaj vodič.
Pravila canonical URL-a za lokalizirane stranice#
Dobar default:
/en/blog/postcanonical je sam sebi/hr/blog/postcanonical je sam sebi- nikad ne canonicalizirati sve localeove na homepage jednog jezika
Canonical tagovi služe za duplikate sadržaja, ne za “preferirani primarni jezik”. Ako canonicalizirate hrvatski na engleski, govorite Googleu da ignorira hrvatski URL.
hreflang alternati s x-default#
Za svaku stranicu ispišite alternates za svaki locale i x-default koji pokazuje na selector jezika ili default locale rutu. Mnogi timovi postavljaju x-default na /en, što je prihvatljivo ako je engleski vaš stvarni default.
U App Routeru implementirajte generateMetadata u svakoj ruti ili u shared helperu.
import type { Metadata } from "next";
import { locales, defaultLocale, type Locale } from "@/i18n/config";
const siteUrl = "https://samioda.com";
export async function generateMetadata(props: {
params: Promise<{ locale: Locale; slug: string }>;
}): Promise<Metadata> {
const { locale, slug } = await props.params;
const canonical = `${siteUrl}/${locale}/blog/${slug}`;
const languages = Object.fromEntries(
locales.map((l) => [l, `${siteUrl}/${l}/blog/${slug}`])
);
return {
alternates: {
canonical,
languages: {
...languages,
"x-default": `${siteUrl}/${defaultLocale}/blog/${slug}`,
},
},
};
}To proizvodi konzistentne alternates po stranici i izbjegava ručne greške.
Lokalizirani naslovi i opisi#
Ako su vam metapodaci prevedeni, osigurajte da dolaze iz istog izvora kao i sadržaj te da ne nedostaju za neke localeove. Nedostajući metapodaci često dovode do fallback naslova poput “Blog”, što smanjuje CTR.
Praktično pravilo:
- za svaki locale zahtijevajte
title,descriptioni ekvivalenteog:title - ako nedostaje, neka CI “sruši” build za marketing sadržaj
Sitemap i robots razmatranja#
Pobrinite se da sitemap uključuje sve locale URL-ove. Ako sitemape generirate dinamički, uključite locale segment u svaki entry.
Ako iz sitemap-a izostavite non-default localeove, Google će ih i dalje naći putem linkova, ali indeksiranje je sporije i manje predvidljivo.
ℹ️ Napomena: Ako preusmjeravate
/na locale, držite redirect stabilnim i izbjegavajte česte promjene. Google česte promjene ponašanja redirecta tretira kao signal nestabilnosti, što može usporiti crawl i re-crawl.
# Upravljanje prijevodima: JSON, CMS ili lokalizacijske platforme#
Prevođenje nije samo “gdje stringovi žive”. Utječe na ritam isporuke, tko može uređivati sadržaj i kako sprječavate “razbijene” layoutove.
Evo tri provediva workflowa s praktičnom tablicom odluke.
| Workflow | Najbolje za | Tipičan tim | Prednosti | Nedostaci |
|---|---|---|---|---|
| JSON rječnici u repozitoriju | UI stringovi, male stranice | Inženjeri | Brzo, verzionirano, jednostavan review | Uređivanje bez developera je bolno, veći diffovi |
| Lokalizirana polja u headless CMS-u | Marketing stranice, blogovi | Marketing + dev | Editorial workflow, previewi, strukturirani sadržaj | Treba governance, dizajn content modela |
| Lokalizacijska platforma sa sinkronizacijom | Aplikacije s čestim izdanjima | Produkt timovi | Translation memory, QA provjere, automatizacija | Dodatni trošak, složenija integracija |
Ako evaluirate CMS opcije za modeliranje lokaliziranog sadržaja, koristite ovu usporedbu kao polazište: Usporedba headless CMS-ova 2026.
Opcija 1: JSON rječnici za UI stringove#
Za produkt UI, JSON rječnici često su najpouzdaniji. Verzijski su kontrolirani, reviewani i deployaju se zajedno s kodom.
Minimalni primjer:
{
"nav.home": "Home",
"nav.blog": "Blog",
"cta.bookCall": "Book a call"
}Držite se ovih pravila:
- koristite stabilne ključeve, ne engleski tekst kao ključ
- validirajte missing keys u CI-ju
- držite UI tekst izvan CMS-a ako trebate strogu kontrolu nad releaseovima
Opcija 2: CMS za marketing stranice i blog sadržaj#
CMS je pravi alat kada se sadržaj mijenja tjedno, a ne po releaseu. Za i18n, modelirajte sadržaj s:
- baznim ID-om dokumenta
- lokaliziranim poljima po localeu
- slugom po localeu ako prevodite URL-ove
- lokaliziranim SEO poljima
Primjer strukture sadržaja:
| Polje | Tip | Lokalizirano | Napomene |
|---|---|---|---|
| id | string | ne | Stabilan identifikator |
| slug | string | da | Koristi se za lokalizirano rutiranje |
| title | string | da | Prikaz i SEO |
| description | string | da | Meta opis |
| body | rich text | da | Glavni sadržaj |
| canonicalOverride | string | da | Rijetko potrebno, ali korisno |
To izbjegava kaos “više dokumenata po jeziku” gdje urednici slučajno odjave (unpublish) jedan locale.
Opcija 3: Lokalizacijske platforme za skaliranje#
Ako isporučujete na 5+ localeova ili izdajete tjedno, lokalizacijske platforme smanjuju churn kroz:
- translation memory
- ponovnu upotrebu stringova
- enforcement glosara
- automatizirane provjere za varijable i placeholder-e
Ključno je integrirati ih s CI-jem kako bi missing translations “pukle” prije produkcije.
# Zamke cacheiranja: CDN, Next cache i locale varijante#
Problemi s cacheiranjem i i18n odgovorni su za mnoge produkcijske incidente tipa “nasumičan jezik”.
Osnovno pravilo: cache se mora razlikovati po URL-u, ne po headerima#
Ako se /pricing renderira na engleskom za jednog korisnika, a na hrvatskom za drugog, ali URL je isti, vaš CDN može cacheirati prvi odgovor i poslužiti ga svima.
URL-ovi s prefiksom jezika to izbjegavaju. /en/pricing i /hr/pricing automatski su različiti cache ključevi.
Česte greške u cacheiranju i rješenja#
| Problem | Simptom | Uzrok | Rješenje |
|---|---|---|---|
| Poslužuje se pogrešan jezik | Korisnici vide pomiješane jezike | Cache ključan samo po putanji bez localea | Koristite locale u URL-u, izbjegavajte renderiranje po headeru |
| Redirect loop | / se stalno preusmjerava | Nesklad cookie vs URL | URL locale pobjeđuje, redirect samo na non-locale putanjama |
| Zastarjeli prijevodi | Novi tekst se ne vidi | ISR ili fetch cache se ne invalidira | Revalidacija po tagovima po localeu i tipu sadržaja |
| Duplikatno indeksiranje | Google nalazi više varijanti | Query parametri ili varijante s trailing slashom | Normalizirajte URL-ove, postavite canonicals, izbjegnite indeksiranje parametara |
Revalidacija po localeu#
Ako revalidirate sadržaj, uključite locale u cache tagove. Inače, revalidacija engleskog može slučajno izbaciti hrvatski ili obrnuto, ovisno o strategiji tagiranja.
Praktična shema imenovanja:
post:123:enpost:123:hr
Držite tagove determinističkima i kratkima kako biste izbjegli operativnu zbrku.
💡 Savjet: Za višejezične marketing stranice preferirajte rute s prefiksom jezika plus statičku generaciju za većinu stranica. Dobivate brži TTFB i manje cache edge caseova nego kod potpuno dinamičkog, header-driven renderiranja.
# Zamke canonical URL-a i duplikatnog sadržaja#
i18n povećava broj URL-ova, pa se male nedosljednosti brzo multipliciraju.
Zamka 1: Canonical uvijek pokazuje na default locale#
Ovo je česta greška kada timovi hardcodaju canonicals u shared komponenti. Rezultat je da non-default localeovi možda nikad neće rangirati jer ste Googleu rekli da su duplikati.
Rješenje: canonical izračunavajte iz params.locale i parametara rute svaki put.
Zamka 2: hreflang nedostaje na dinamičkim stranicama#
Ako blog koristi dinamičke segmente, možete imati hreflang na category stranicama, ali ne i na stranicama postova.
Rješenje: centralizirajte generiranje alternates i pozivajte ga iz svake rute koja treba biti indeksirana.
Zamka 3: Prevodite sadržaj, ali ne prevodite interne linkove#
Ako engleski sadržaj linka na /en/contact, hrvatska verzija treba linkati na /hr/contact. Inače “curite” korisnike između localeova i šaljete pomiješane signale crawlerima.
Rješenje: implementirajte locale-aware link helper i provedite ga u alatima za sadržaj. U CMS rich textu spremite reference na interne stranice i resolveajte ih po localeu.
Zamka 4: Parametrizirani URL-ovi indeksirani kao odvojene stranice#
UTM parametri i filteri mogu stvarati duplikatne stranice kroz jezike.
Rješenje:
- držite canonicals čistima
- konfigurirajte analitiku da ignorira nebitne parametre
- izbjegavajte interno linkanje na parametrizirane URL-ove
Za širu SEO higijenu koja se presijeca s i18n, uskladite se s praksama u Zašto Next.js za SEO.
# Praktičan workflow za sadržaj koji ne razbija buildove#
Evo workflowa koji dobro funkcionira za timove s inženjerima i marketingom:
- 1UI stringovi žive u JSON rječnicima u repozitoriju.
- 2Marketing stranice i blog sadržaj žive u headless CMS-u s lokaliziranim poljima.
- 3Mapiranje slugova se provodi u CMS-u. Ako slug za locale nedostaje, stranica se ne objavljuje za taj locale.
- 4CI provjere osiguravaju:
- da svaka objavljena stranica ima
titleidescriptionpo localeu - da se ne referenciraju nepodržani localeovi
- da sitemap sadrži sve objavljene locale URL-ove
Time sprječavate dva najskuplja kvara:
- razbijenu navigaciju kroz localeove
- slučajno indeksiranje nepotpunih prijevoda
# Ključne poruke#
- Koristite rutiranje s prefiksom jezika u App Routeru jer caching i SEO čini determinističkima i izbjegava varijacije sadržaja po headerima.
- Detekciju jezika implementirajte samo za non-locale putanje poput
/, uz jasan redoslijed prioriteta: URL, cookie, header, default. - Generirajte ispravne SEO metapodatke po localeu: canonicals koji referenciraju sami sebe, pune
hreflangalternates ix-defaultentry. - Workflowe prevođenja birajte prema tipu sadržaja: JSON za UI stringove, CMS za editorial sadržaj i lokalizacijske platforme za izdanja visoke frekvencije.
- Dinamičke segmente tretirajte kao problem content modela: rano odlučite jesu li slugovi stabilni, prevedeni ili ID-based i generirajte static params za svaki locale.
- Spriječite produkcijske incidente tako da cache varira po URL-u i da tagirate revalidaciju po localeu i entitetu sadržaja.
# Zaključak#
Solidan Next.js i18n App Router setup manje je o prevođenju stringova, a više o izgradnji predvidljivih URL-ova, stabilne detekcije i SEO signala koji se skaliraju na desetke ili tisuće stranica. Kad ispravno složite rutiranje, canonicals i cacheiranje, izbjegavate najčešće višejezične failove: odgovore na pogrešnom jeziku, duplikatno indeksiranje i neodrživu logiku slugova.
Ako želite da Samioda implementira višejezično rutiranje, SEO-sigurne metapodatke i workflow za sadržaj koji vaš tim može voditi bez uskih grla na developerima, javite se i zajedno ćemo isplanirati i18n arhitekturu koja odgovara vašim localeovima, CMS-u i procesu izdanja.
FAQ
Više iz kategorije Web razvoj
Sve →React obrasci u velikim aplikacijama: React Hook Form + Zod obrasci za složene proizvode
Najbolje prakse za React obrasce u velikim aplikacijama uz React Hook Form i Zod: validacija po shemi, višekratno upotrebljiva polja, asinkrone provjere, višekoračni tokovi, performanse, pristupačnost i obrasci integracije sa serverom/API-jem.
Implementacija Stripe pretplata u Next.js-u: Billing Portal, webhookovi i prava pristupa
Vodič spreman za produkciju za Next.js Stripe pretplate: planovi i probni periodi, Billing Portal, provjera webhookova, idempotentna obrada, mapiranje prava pristupa i testiranje sa Stripe CLI-jem.
Objašnjene strategije cacheiranja u Next.js-u: SSR, SSG, ISR, Route Cache i SWR
Praktičan vodič kroz Next.js strategije cacheiranja u eri App Routera — kako se SSR, SSG, ISR, Route Cache, Data Cache i SWR uklapaju u cjelinu, uz tablice za odluke, primjere koda i česte zamke poput zastarjelih auth i tenant podataka.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Kontrolni popis za migraciju na Next.js App Router (s Pages Routera) + česte zamke
Praktičan, korak-po-korak plan migracije na Next.js App Router s Pages Routera, uključujući kontrolni popis za routing, dohvat podataka, SEO metadata, deployment i vodič za rješavanje čestih zamki.
Objašnjene strategije cacheiranja u Next.js-u: SSR, SSG, ISR, Route Cache i SWR
Praktičan vodič kroz Next.js strategije cacheiranja u eri App Routera — kako se SSR, SSG, ISR, Route Cache, Data Cache i SWR uklapaju u cjelinu, uz tablice za odluke, primjere koda i česte zamke poput zastarjelih auth i tenant podataka.
Zašto Je Next.js Najbolji Framework za SEO u 2026
Saznajte zašto Next.js dominira SEO performansama u 2026. Server-side rendering, Core Web Vitals, strukturirani podaci i stvarne usporedbe performansi.