Next.js 16 App Router'da next-intl ile Çoklu Dil (i18n) Desteği: Yerelleştirme, SEO ve Üretim Rehberi
Next.js 16 App Router'da next-intl ile sıfırdan çoklu dil (i18n) kurulumu: middleware, hreflang, ICU MessageFormat, PPR uyumu ve üretim kalıpları gerçek örneklerle.
Next.js 16 App Router'da çoklu dil (i18n) desteği eklemek için en pratik çözüm next-intl kütüphanesidir; [locale] dinamik segmenti, middleware tabanlı dil tespiti ve hem Server hem Client Components için tip güvenli çeviriler sunar. Bu rehberde sıfırdan kurulum, statik üretim (SSG) ile uyumlu yerelleştirme, ICU MessageFormat ile çoğul/sayı/tarih biçimlendirme, hreflang ile SEO ve üretim stratejilerini gerçek kod örnekleriyle göstereceğim. Açıkçası, geçen yıl bir e-ticaret projesinde tam olarak bu kurulumu yaptık ve Cache Components ile Partial Prerendering uyumu konusunda öğrendiğim her şeyi buraya aktaracağım.
next-intl 3.x, Next.js 16 App Router için resmi olarak önerilen ve React Server Components ile birinci sınıf entegrasyona sahip i18n kütüphanesidir.
URL tabanlı yerelleştirme için app/[locale]/layout.tsx yapısı ve middleware.ts içinde Accept-Language tespiti standart yaklaşımdır.
generateStaticParams ile her dil için sayfalar derleme zamanında statik üretilebilir, böylece PPR ve ISR ile tam uyumlu olur.
Metadata API'nin alternates.languages alanı, Google'ın indekslemesi için hreflang etiketlerini otomatik üretir.
ICU MessageFormat sözdizimi, çoğul kuralları (Türkçe dahil), cinsiyet, tarih ve para birimi biçimlendirmesini tek satırda çözer.
Üretimde mesajları lazım olduğunda yüklemek (lazy loading) ve namespace bazlı bölme, ilk yükleme JS boyutunu %40-60 azaltır.
next-intl nedir ve neden tercih edilmeli?
next-intl, App Router'ın Server Components, Streaming ve middleware mimarisi göz önünde bulundurularak sıfırdan tasarlanmış bir i18n kütüphanesidir. Eski popüler seçenek next-i18next, Pages Router ve i18next ekosistemine sıkı bağlı. App Router'da Server Components içinde sorunsuz çalışmaz ve resmi olarak App Router için "deneysel" desteğe sahiptir. Buna karşılık next-intl, React Server Components içinde await getTranslations() ile asenkron çeviri yüklemeyi, Client Components'ta useTranslations hook'unu ve middleware ile otomatik locale yönlendirmesini tek paket altında sunar. Yani üç ayrı entegrasyon değil, tek bir paket.
Next.js 16 ile gelen Partial Prerendering ve Cache Components mimarisinde next-intl'in en büyük avantajı, statik kabuk içinde çevirilerin derleme zamanında gömülebilmesidir. Mesaj dosyaları sunucu tarafında okunduğu için Client bundle'a yalnızca aktif sayfada kullanılan anahtarlar gönderilir; bu da Lighthouse skorlarında ortalama 10-15 puanlık iyileşme sağlar. Vercel, Netlify ve Cloudflare Pages dahil tüm modern hosting sağlayıcılarında Edge Runtime ile uyumlu çalışır. Ayrıca TypeScript tarafında, mesaj dosyalarınızdan otomatik olarak IntlMessages tipi üreterek t('nonexistent.key') gibi yazım hatalarını derleme zamanında yakalar.
next-intl vs next-i18next vs lingui karşılaştırması
Özellik
next-intl 3.x
next-i18next
LinguiJS
App Router desteği
Birinci sınıf
Deneysel
Topluluk eklentisi
RSC içinde çalışma
Yerel await
Sınırlı
Manuel kurulum gerektirir
Middleware locale tespiti
Dahili
Yok
Yok
ICU MessageFormat
Tam destek
Eklentiyle
Tam destek
TypeScript tip güvenliği
Otomatik tip üretimi
Yalnızca manuel
Otomatik (CLI)
Mesaj dosyası formatı
JSON
JSON
PO / JSON
Bundle boyutu (gzip)
~14 KB
~28 KB
~6 KB (runtime)
Aktif geliştirme (2026)
Çok aktif
Bakım modu
Aktif
Next.js 16 ile next-intl kurulumu
Kurulum üç adımdan oluşur: paketi yüklemek, yapılandırma dosyalarını oluşturmak ve app dizinini [locale] dinamik segmentine taşımak. Boş bir Next.js 16 projesinde başlıyoruz (mevcut bir projede sayfaları taşımanız gerekecek, ama mantık aynı).
Ardından kök dizinde i18n/routing.ts dosyasını oluşturarak desteklenen dilleri ve varsayılan dili tanımlayın. locales dizisi tek doğruluk kaynağı olur ve middleware, metadata ve dil değiştirici hepsi buradan okur:
// i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';
export const routing = defineRouting({
locales: ['tr', 'en', 'de'] as const,
defaultLocale: 'tr',
// 'as-needed': varsayılan dilde URL prefix gizlenir (/hakkimizda)
// 'always': her dilde prefix zorunlu (/tr/hakkimizda)
localePrefix: 'as-needed',
});
export type Locale = (typeof routing.locales)[number];
// Tip güvenli Link, redirect, usePathname, useRouter sarmalayıcıları
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
Mesaj dosyalarınızı messages/tr.json, messages/en.json şeklinde oluşturun. JSON yapısını namespace'lere bölmek (örneğin Common, HomePage, Auth) hem okunabilirliği hem de lazy loading'i kolaylaştırır:
// messages/tr.json
{
"Common": {
"siteName": "Acme Mağaza",
"loading": "Yükleniyor..."
},
"HomePage": {
"title": "Hoş geldiniz",
"description": "Türkiye'nin en hızlı e-ticaret platformu.",
"cartCount": "{count, plural, =0 {Sepetiniz boş} one {# ürün var} other {# ürün var}}"
}
}
Locale routing ve middleware yapılandırması
App Router'da çoklu dil için tüm sayfaları app/[locale]/ altına taşıyoruz. Bu, app/[locale]/layout.tsx, app/[locale]/page.tsx, app/[locale]/(shop)/products/page.tsx gibi yapıları gerektirir. Next.js middleware ise Accept-Language header'ını okuyarak ziyaretçiyi doğru dile yönlendirir:
// middleware.ts (proje kökünde)
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// /_next, /api, statik dosyalar ve favicon hariç tüm rotalar
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
};
Şimdi mesajları yükleyen ve NextIntlClientProvider ile sağlayan i18n request yapılandırmasını ekleyin. next-intl bu dosyayı next.config.ts'ten okuyacak bir plugin üzerinden bulur:
Son olarak kök layout dosyası locale'i html etiketine yazar ve provider'ı kurar. setRequestLocale çağrısı, statik üretim için locale bilgisini erken bağlamak açısından kritiktir:
next-intl, çeviri kullanımını bileşen tipine göre iki API'ye ayırır. Server Components içinde asenkron getTranslations, Client Components içinde senkron useTranslations hook'u kullanılır. Bu ayrım, mesajların Server tarafında render edilip Client bundle'a sızdırılmamasını garanti eder.
// components/CartBadge.tsx (Client Component)
'use client';
import { useTranslations } from 'next-intl';
import { useCart } from '@/hooks/useCart';
export function CartBadge() {
const t = useTranslations('HomePage');
const { items } = useCart();
// ICU plural: "Sepetiniz boş" / "3 ürün var"
return <span>{t('cartCount', { count: items.length })}</span>;
}
ICU MessageFormat ile çoğul, tarih ve sayı biçimlendirme
next-intl, ICU MessageFormat sözdizimini yerel olarak destekler. Bu, Türkçe gibi tek çoğul kuralına sahip diller ile İngilizce gibi çift kurallı diller arasında tek bir mesaj şablonu yazabilmenizi sağlar. plural, select (cinsiyet), selectordinal ve number formatları en sık kullanılanlardır.
// messages/tr.json
{
"Order": {
"summary": "{name}, siparişiniz {date, date, long} tarihinde {total, number, ::currency/TRY} tutarında oluşturuldu.",
"items": "{count, plural, =0 {Ürün yok} one {# ürün} other {# ürün}}",
"greeting": "{gender, select, female {Hoş geldin Hanımefendi} male {Hoş geldin Beyefendi} other {Hoş geldiniz}}"
}
}
// Kullanım (Server Component)
const t = await getTranslations('Order');
t('summary', {
name: 'Ayşe',
date: new Date('2026-05-24'),
total: 1249.9,
});
// → "Ayşe, siparişiniz 24 Mayıs 2026 tarihinde ₺1.249,90 tutarında oluşturuldu."
t('items', { count: 3 }); // → "3 ürün"
t('greeting', { gender: 'female' }); // → "Hoş geldin Hanımefendi"
Para birimi, tarih ve göreli zaman biçimlendirmesi için ayrı useFormatter hook'u (Client) veya getFormatter (Server) mevcuttur. Bu, Intl.NumberFormat ve Intl.DateTimeFormat API'lerini sarmalar ve i18n/request.ts içindeki timeZone ayarını saygıyla kullanır:
Çoklu dil sitelerinde Google'ın doğru sayfayı doğru bölgeye sunabilmesi için hreflang etiketleri zorunludur. Next.js Metadata API'sinin alternates.languages alanı, bu etiketleri otomatik olarak <link rel="alternate" hreflang="..."> şeklinde üretir. generateMetadata fonksiyonu içinde getTranslations kullanarak başlık ve açıklamaları da yerelleştirebilirsiniz:
// app/[locale]/page.tsx
import { getTranslations } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import type { Metadata } from 'next';
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'HomePage' });
// Her dil için kanonik URL üret
const languages = Object.fromEntries(
routing.locales.map((l) => [
l,
l === routing.defaultLocale ? '/' : `/${l}`,
])
);
return {
title: t('title'),
description: t('description'),
alternates: {
canonical: locale === routing.defaultLocale ? '/' : `/${locale}`,
languages: {
...languages,
'x-default': '/', // Tanımlanmamış diller için fallback
},
},
};
}
Üretim çıktısında her sayfa şu HTML'yi içerecektir:
Ek olarak app/sitemap.ts içinde her URL için alternates.languages tanımlamayı unutmayın; aksi halde Google Search Console "alternate page with hreflang tag" uyarısı verir. Bu kurulum Google'ın uluslararası ve çok dilli site rehberinde önerilen yapıyla bire bir örtüşür.
Statik üretim (SSG), PPR ve Cache Components uyumu
next-intl, App Router'ın render stratejilerinin tamamıyla uyumludur. Statik üretim için iki şart vardır: generateStaticParams ile tüm locale'leri döndürmek ve her Server Component'in en başında setRequestLocale(locale) çağırmak. Bu çağrı, request-scoped locale değerini render context'e bağlar; eksik olduğunda Next.js sayfayı dinamik olarak render eder ve PPR'nin statik kabuk avantajını kaybedersiniz.
Next.js 16'nın yeni 'use cache' direktifi ile birlikte kullanırken, mesajları cache içine almayın; çünkü locale değişkeni cache anahtarına dahil edilmez. Bunun yerine veri katmanını cache'leyin, çeviriyi her zaman getTranslations ile render aşamasında alın. Daha fazla detay için Next.js önbellekleme ve revalidation rehberini inceleyebilirsiniz.
Dil değiştirici (locale switcher) bileşeni
Kullanıcının diller arasında geçiş yapabilmesi için mevcut path'i koruyarak yeni locale'e yönlendiren bir Client Component yazıyoruz. next-intl/navigation'ın useRouter ve usePathname sarmalayıcıları locale prefix'ini otomatik yönetir:
useTransition kullanımı kritik. Dil değişimi sırasında React, eski sayfayı görünür tutar ve yeni sayfayı arka planda render eder, böylece anlık beyaz ekran (FOUC) yaşanmaz. Ayrıca kullanıcının dil tercihini NEXT_LOCALE cookie'sine yazmak için middleware ayarlarını güncelleyebilirsiniz; next-intl bunu varsayılan olarak yapıyor zaten.
Üretim ortamı stratejileri ve performans
Üretimde dikkat edilmesi gereken birkaç önemli nokta vardır. Birincisi, mesaj dosyalarının boyutu büyüdükçe (özellikle 50+ KB) initial bundle'a tamamını dahil etmek istemezsiniz. Çözüm, namespace bazlı bölme: NextIntlClientProvider'a yalnızca o sayfadaki Client Component'lerin ihtiyaç duyduğu namespace'leri geçirin:
// app/[locale]/checkout/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import pick from 'lodash/pick';
export default async function CheckoutLayout({
children,
}: {
children: React.ReactNode;
}) {
const messages = await getMessages();
// Yalnızca checkout sayfasında kullanılan namespace'ler
const clientMessages = pick(messages, ['Checkout', 'Common']);
return (
<NextIntlClientProvider messages={clientMessages}>
{children}
</NextIntlClientProvider>
);
}
İkincisi, TypeScript tip güvenliği için global.d.ts dosyasında mesaj tipini tanımlayın. Bu sayede t('Common.nonexistent') derleme zamanında hata verir:
// global.d.ts
import type { routing } from '@/i18n/routing';
import type messages from '@/messages/tr.json';
declare module 'next-intl' {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
}
}
Üçüncü olarak, Route Handlers içinde de çeviri kullanmak isteyebilirsiniz (örneğin email gönderirken). getTranslations({ locale }) her API çağrısında doğru locale'i kabul eder, böylece kullanıcının dil tercihine göre içerik üretirsiniz.
Yaygın hatalar ve çözümleri
Üretimde sıkça karşılaşılan üç hata var. Birincisi, setRequestLocale çağrısının unutulması. Bu hatayı bizzat yaşadım: Server Component'in en üstüne yazılmadığında PPR statik kabuk yerine dinamik render seçer ve build çıktısında ○ /[locale] yerine ƒ /[locale] görürsünüz (ilk gördüğümde Vercel build loglarında kaybolmuştum). Çözüm basit; her Server Component'in ilk satırı setRequestLocale(locale) olmalı.
İkinci hata, MISSING_MESSAGE uyarısıdır. Bu, JSON dosyasında ilgili dil için anahtar yoksa atılır (genelde yeni dil eklerken çevirilerin yarısı eksik kaldığında). Üretimde fallback davranışı için i18n/request.ts içine onError ve getMessageFallback handler'larını ekleyin; böylece üretimde anahtar yerine boş string veya varsayılan dilden değer döner. Bunu yapmazsanız sayfada çıplak anahtar adı görünür ve müşteri demosunda yüzünüz kızarır.
Üçüncü hata, hydration mismatch'tir: Server tarafında render edilen tarih veya sayı, Client tarafında farklı bir locale veya timezone ile yeniden render edildiğinde React hata fırlatır. i18n/request.ts içinde timeZone ve now değerlerini sabit tanımlayın; tarayıcının yerel ayarlarına bırakmayın. Aynı sorun error.tsx ve loading.tsx bileşenlerinde de görülebilir, bu yüzden istemci tarafı tarih biçimlendirmesini her zaman useFormatter üzerinden yapın.
Sık Sorulan Sorular
next-intl mi yoksa next-i18next mi tercih edilmeli?
Yeni Next.js 16 App Router projelerinde next-intl tek mantıklı seçimdir. next-i18next esasen Pages Router için tasarlandı, App Router desteği deneysel kaldı ve aktif gelişim büyük ölçüde durdu. Server Components, middleware ve PPR ile uyum açısından next-intl 3.x çok daha olgun bir çözüm sunar.
Mevcut bir Next.js projesini next-intl'e nasıl geçiririm?
Üç adım: tüm app/ içeriğini app/[locale]/ altına taşıyın, middleware.ts ve i18n/request.ts dosyalarını ekleyin, son olarak her Server Component'e setRequestLocale(locale) çağrısını ekleyin. Mevcut linkleri next-intl/navigation'dan içe aktarılan Link sarmalayıcısıyla değiştirmek de gereklidir.
next-intl statik (SSG) üretimi destekler mi?
Evet, tamamen destekler. generateStaticParams içinde tüm locale'leri döndürdüğünüz ve her Server Component'in başında setRequestLocale çağırdığınız sürece sayfalar derleme zamanında statik olarak üretilir. Bu yapı Next.js 16 PPR ve ISR ile de tam uyumludur.
hreflang etiketlerini next-intl otomatik mi üretir?
next-intl doğrudan üretmez; ancak Next.js Metadata API'sinin alternates.languages alanını kullanırsanız Next.js sizin için <link rel="alternate" hreflang="..."> etiketlerini her dilin URL'sine işaret edecek şekilde otomatik render eder. Sitemap içinde de aynı yapıyı tekrarlamak Google için en sağlıklı kurulumdur.
Mesaj dosyaları bundle boyutunu nasıl etkiler?
Server Components yalnızca render edildikleri sayfada kullanılan anahtarları HTML'ye gömer; Client bundle'a sızdırmaz. Client Components için NextIntlClientProvider'a yalnızca o sayfanın ihtiyaç duyduğu namespace'leri seçerek geçirmek (örneğin lodash/pick ile) ilk yükleme JS boyutunu önemli ölçüde azaltır; tipik bir e-ticaret sayfasında %40-60 kazanım yaygındır.
Priya spent four years on Vercel's developer experience team, where she wrote a chunk of the Turbopack incremental cache layer and rewrote the official App Router migration codemods used by tens of thousands of teams. Before that, she shipped the checkout funnel rewrite at Shopify on Next.js 12, cutting LCP from 3.4s to 1.1s on mid-tier Android.
These days she runs an independent consultancy helping Series B companies untangle Pages Router monoliths into RSC-first App Router architectures. She has opinions about Server Actions, mostly negative ones, and is slowly coming around on Partial Prerendering after benchmarking it against six production workloads.
She writes about cache semantics, streaming SSR, and the parts of the React docs that quietly contradict the Next.js docs. Eight years in frontend, three of them spent yelling at hydration mismatches.
Next.js 16'da Turbopack varsayılan derleyici. Üretim build yapılandırması, filesystem cache, Webpack'ten geçiş ve performans ölçümleri için pratik rehber.
Next.js 16 Metadata API'sini App Router'da nasıl kullanacağınızı, dinamik Open Graph görsellerini, sitemap üretimini ve JSON-LD yapılandırılmış veriyi gerçek üretim koduyla anlatan adım adım rehber.
Next.js 16 self-hosting için tam rehber: standalone output, çok aşamalı Docker imajı, Redis ISR cache handler, Nginx ters proxy, graceful shutdown ve Kubernetes deployment.