SvelteKit ile Çok Dilli SEO Kurulumu: i18n ve Hreflang Entegrasyonu

SvelteKit route bazlı i18n mimarisinde hreflang ve canonical kurulumunu temsil eden editoryal blog görseli

SvelteKit projelerinde çok dilli yapı kurmak, Next.js veya Nuxt'a kıyasla çok daha az hazır kalıpla başlıyor. Framework'ün kendisi bir i18n çözümü dayatmıyor; dil yönetimini sveltekit-i18n, typesafe-i18n veya son dönemde hızla yaygınlaşan inlang/paraglide-js gibi bağımsız kütüphanelere bırakıyor. Bu esneklik, ekip için hem bir avantaj hem de bir risk kaynağı: doğru kurulan bir yapı son derece hafif ve hızlı çalışıyor, ama hreflang ve canonical gibi teknik SEO katmanları hiçbir kütüphanenin "kutudan çıktığı gibi" çözdüğü bir alan değil.

Route mimarisi kararı burada erken ve tek yönlü bir karar. SvelteKit'in dosya tabanlı routing sistemi, dil önekini [lang] gibi bir dinamik segment olarak modellemeye uygun, ama bu segmenti kurduktan sonra o segmentin her sayfada tutarlı çalışması, locale tespitinin doğru katmanda yapılması ve üretilen HTML'in gerçekten dil bazında ayrışması gerekiyor. Aksi halde ortaya çıkan şey, URL'de dil kodu taşıyan ama içerik ve meta katmanında tek bir dile sabitlenmiş yarım bir çok dilli site oluyor.

SvelteKit'te risk katman katman dağılır: dil öneki URL'de yoksa içerik oturuma kilitlenir, hreflang hiçbir kütüphaneden gelmez, reroute Googlebot'u başka dile çevirebilir, canonical tarayıcı URL'sinden basılırsa self-referencing bozulur. Hangi katmanın kırıldığı, prerender mı SSR mi seçildiğinden bağımsız aynı kontrol listesine iner.

SvelteKit'te route bazlı i18n hangi katmanda kurulur?

En yaygın yaklaşım, kök dizinde src/routes/[lang]/ şeklinde bir dinamik segment açmak ve tüm sayfa ağacını bu segmentin altına taşımak. Bu yapı, her dil sürümüne kendi URL kökünü veriyor ve +layout.server.ts içinde params.lang değerini okuyarak o dile ait mesaj kataloğunu yükleyebiliyorsunuz. Paraglide gibi derleme zamanlı çözümler farklı bir yol izliyor: mesajlar build sırasında dile göre ayrıştırılan statik modüllere dönüşüyor, çalışma zamanında büyük bir JSON dosyası indirilmiyor.

İkinci yaklaşım, dil önekini URL'de tutmadan hooks.server.ts içindeki bir middleware ile locale'i cookie veya Accept-Language başlığından çözmek. Bu yöntem kullanıcı deneyimi açısından bazen tercih ediliyor, ama SEO açısından ciddi bir zayıflık taşıyor: her dil versiyonu aynı URL'de yaşıyorsa, o sayfanın hangi dilde indeksleneceğini Google değil sunucu tarafındaki oturum durumu belirliyor. Bu senaryonun ürettiği risk, Googlebot'un render kuyruğunda çok dilli içeriği nasıl işlediğine dair bilinen sorunlarla aynı köke iniyor: URL ile içerik arasında sabit bir eşleşme yoksa, Googlebot'un gördüğü sürüm ile kullanıcının gördüğü sürüm zamanla ayrışabiliyor.

Pratik karar kuralı basit: dil, URL segmentinde açıkça görünmeli. Cookie tabanlı dil tespiti yalnızca varsayılan yönlendirme için kullanılmalı, sayfanın URL'ini ve içeriğini belirlemek için değil.

Hreflang etiketleri SvelteKit'te kendiliğinden üretilmiyor

Ne sveltekit-i18n ne de paraglide-js, hreflang meta etiketlerini otomatik olarak head'e ekliyor. Bu kütüphaneler mesaj çevirisini ve route eşlemesini çözüyor; hreflang üretimi tamamen projeye ait bir sorumluluk olarak kalıyor. Uygulamada bu, her sayfanın +layout.svelte veya sayfa bazlı bileşeninde, o sayfanın tüm dil karşılıklarının URL listesini üreten bir yardımcı fonksiyon yazmak anlamına geliyor.

Bu fonksiyonun girdisi genellikle +layout.server.ts'ten gelen route eşleme tablosu oluyor: her slug'ın hangi dilde hangi karşılığa sahip olduğunu tutan statik bir veri yapısı. Slug çevirisi olmayan, yani her dilde aynı slug'ı kullanan projelerde bu basit; slug'lar dile göre değiştiğinde (örneğin /urunler/ ile /products/) eşleme tablosunun eksiksiz ve güncel tutulması gerekiyor, çünkü eksik bir eşleme doğrudan karşılıksız kalan bir hreflang girdisi üretiyor.

x-default değerinin hangi URL'i karşılayacağı kararı da bu eşleme tablosundan bağımsız değil: proje genelde bir varsayılan locale tanımlıyorsa, x-default o locale'in kök URL'ine işaret etmeli ve bu değer route eşleme tablosunda ayrı bir sabit olarak tutulmalı, sayfa bazında elle yazılmamalı. Elle yazım, sayfa sayısı arttığında en sık gözden kaçan tutarsızlık kaynağı oluyor.

Yardımcı fonksiyonun ürettiği çıktıyı doğrulamak, her dağıtım sürecine dahil edilmesi gereken bir adım; projenin ilk yayınına özgü tek seferlik bir kontrol değil. Slug çevirisi olan projelerde eşleme tablosuna yeni bir sayfa eklendiğinde hreflang seti otomatik olarak genişlemiyorsa, ya fonksiyonun girdi kaynağı güncellenmemiştir ya da yeni slug, dil bazındaki eşleme tablosuna hiç girmemiştir. İkisi de sessiz hata üretiyor. Hreflang seti eksik ama sayfa yine de yayında, Google bir süre sonra yanlış sürümü öne çıkarıyor. Sorunun kaynağını bulmak sayfa sayısı arttıkça zorlaşıyor.

reroute hook ve locale tespiti: otomatik yönlendirme nerede risk üretir?

SvelteKit'in hooks.server.ts içinde tanımlanan reroute mekanizması, gelen bir isteği farklı bir route'a eşlemeye izin veriyor. Bazı projeler bunu, kullanıcının tarayıcı dilini algılayıp otomatik olarak ilgili dil segmentine yönlendirmek için kullanıyor. Kullanıcı deneyimi açısından cazip görünen bu davranış, Googlebot'un isteğine de aynı şekilde uygulandığında ciddi bir tarama sorunu üretiyor: Googlebot'un gönderdiği Accept-Language başlığı sabit ve öngörülebilir değil, bu nedenle aynı URL farklı taramalarda farklı dile yönlendirilebiliyor.

Daha güvenli model, reroute veya benzeri bir yönlendirmeyi yalnızca kök URL (/) için çalıştırmak ve dil segmenti zaten URL'de mevcutsa hiçbir yönlendirme yapmamak. Kullanıcı /en/urun-adi adresine doğrudan geldiğinde, sunucu bu isteği hiçbir koşulda başka bir dile çevirmemeli; aksi halde bot ve gerçek kullanıcı arasında görülen içerik farklılaşıyor ve bu, cloaking şüphesine yaklaşan bir davranış olarak değerlendirilme riski taşıyor.

Yönlendirme kararı verilirken kullanılan durum kodu da ayrı bir dikkat noktası: dil algılamaya dayalı kök yönlendirmesi 302 ile yapılmalı, çünkü bu yönlendirme kalıcı bir URL kararı değil, oturum bazlı bir tahmin. 301 kullanımı, arama motoruna kök URL'in artık her zaman o dile ait olduğu sinyalini veriyor ve bu, farklı bir dilde arama yapan kullanıcılar için yanlış bir kalıcılık iddiası oluşturuyor.

Adapter seçimi de reroute davranışını dolaylı biçimde etkiliyor. adapter-node ile dağıtılan projelerde reroute mantığı sunucu süreci boyunca aynı bağlamda çalışıyor; adapter-vercel ya da adapter-cloudflare gibi edge tabanlı çözümlerde ise her istek bağımsız bir işlev çağrısında değerlendiriliyor. Bu fark, locale tespiti için kullanılan önbellek veya oturum verilerinin erişilebilirliğini etkiliyor. Projeyi edge ortamına taşırken reroute içinde kullanılan veri kaynaklarının bu ortamda da beklendiği gibi çalıştığını ayrıca doğrulamak gerekiyor. Lokal geliştirme ortamında sorunsuz işleyen bir locale tespiti, edge işlev sınırlarında sessizce varsayılan değere düşebiliyor; hangi sayfanın hangi dile yönlendirildiği öngörülemez hale geliyor ve hata log'a yansımıyor.

Canonical URL ve locale parametresi: hangi sürüm yetkili?

SvelteKit'te canonical etiketi, framework tarafından üretilmiyor; svelte:head içinde $page.url üzerinden manuel olarak yazılıyor. Buradaki en sık görülen hata, canonical URL'in query string veya trailing slash gibi teknik varyasyonları temizlemeden doğrudan $page.url.href değerini basmak. Aynı sayfaya farklı sıralamada query parametreleriyle gelen istekler, her biri kendini canonical gösteren farklı URL varyasyonları üretebiliyor; bu da çok dilli sitelerde canonical yönetiminin temel kuralı olan self-referencing tutarlılığı zayıflatıyor.

Doğru yaklaşım, canonical URL'i route eşleme tablosundan türetmek, tarayıcı URL'inden değil. Route tablosu her dil ve slug kombinasyonu için tek bir yetkili URL tanımlıyorsa, canonical etiketi her zaman bu tablodan okunuyor ve tarayıcıdaki gerçek URL'de olabilecek küçük farklar (büyük-küçük harf, sıralama, iz bırakan slash) canonical kararını hiç etkilemiyor.

Locale parametresinin URL'de nerede durduğu da canonical kararını şekillendiriyor. Segment tabanlı yapıda (/tr/urun) canonical zaten kendi dilini taşıyor ve başka bir karar gerekmiyor. Query parametresi tabanlı bir dil seçimi (?lang=tr) kullanılıyorsa, bu yapı SEO açısından zaten önerilmiyor, çünkü query parametresi Google tarafından genellikle görmezden gelinen bir sinyal katmanı ve hreflang'ın gerektirdiği ayrı URL mantığıyla temelden çakışıyor.

Trailing slash tutarsızlığı da SvelteKit projelerinde sık görülen ve canonical kararını bozan bir detay. /tr/urun ile /tr/urun/ SvelteKit'in routing katmanında aynı sayfayı sunabiliyor; ama canonical etiketi bu iki URL'den birini seçmek zorunda. Route tablosunda tercih edilecek form bir kez sabitlendikten sonra, svelte:head içindeki canonical her zaman bu formda üretilmeli. Küçük görünen bu fark, iç linklerin ve hreflang kayıtlarının tutarsız yazıldığı projelerde zaman içinde birden fazla canonical varyantının eşzamanlı olarak görünmesine zemin hazırlıyor.

Prerender, SSR ve adapter seçimi çok dilli SEO'yu nasıl etkiler?

SvelteKit'in prerender seçeneği, sayfa bazında veya route grubu bazında statik HTML üretimine izin veriyor. İçerik nadiren değişen, dil sayısı sabit kalan projelerde tüm dil sürümlerini build zamanında prerender etmek, hreflang ve canonical etiketlerinin her zaman kaynak HTML'de tutarlı şekilde bulunmasını garanti ediyor; CSR kaynaklı render gecikmesi riskini tamamen ortadan kaldıran en güvenli seçenek bu.

Kullanıcıya özel içerik, oturum durumu veya sık güncellenen katalog verisi olan projelerde tam prerender mümkün olmuyor ve SSR adaptörü (adapter-node, adapter-vercel gibi) devreye giriyor. Bu durumda hreflang ve canonical üretimi sunucu tarafında her istekte yeniden hesaplanıyor; bu hesaplamanın maliyeti genellikle ihmal edilebilir düzeyde, ama sunucu tarafında route eşleme tablosunun her istekte yeniden okunmaması, önbelleğe alınmış statik bir modül olarak tutulması performans açısından önemli.

Karma bir strateji de sık görülüyor: statik sayfalar (kurumsal içerik, blog) prerender edilirken, dinamik sayfalar (ürün, kullanıcı paneli) SSR ile sunuluyor. Bu karışık modelde kritik olan tek nokta, iki render modunun ürettiği hreflang ve canonical mantığının aynı ortak yardımcı fonksiyondan beslenmesi; ayrı ayrı yazılan iki mantık, zamanla birbirinden sapıyor ve hangi sayfanın hangi kurala göre üretildiğini takip etmek zorlaşıyor.

Sitemap üretimi ve yayın öncesi doğrulama

SvelteKit'te sitemap, genellikle src/routes/sitemap.xml/+server.ts gibi bir endpoint olarak elle yazılıyor; framework'ün hazır bir sitemap eklentisi yok. Çok dilli bir projede bu endpoint, route eşleme tablosundaki her slug için tüm dil karşılıklarını dolaşıp hreflang ilişkilerini sitemap içinde taşıyan yapıyı üretmeli; aksi halde sitemap sadece URL listesi sunan, dil ilişkisini taşımayan zayıf bir dosyaya dönüşüyor.

Yayın öncesi doğrulama üç adımda yürütülüyor: önce build çıktısında (prerender edilen dosyalarda) her sayfanın kaynak kodunda hreflang setinin eksiksiz göründüğü kontrol ediliyor, ardından SSR ile sunulan sayfalarda aynı kontrol canlı bir istekle tekrarlanıyor, son olarak sitemap endpoint'inin ürettiği URL sayısı ile route eşleme tablosundaki gerçek sayfa sayısı karşılaştırılıyor. Üçü arasında bir sapma varsa, kaynak genellikle eşleme tablosuna yeni bir sayfa eklenip sitemap fonksiyonunun güncellenmemiş olması.

Sitemap endpoint'inin ne ürettiğini gözden geçirme alışkanlığı, projenin büyüdüğü dönemlerde özellikle önem kazanıyor. Yeni bir dil eklenmesi, mevcut tüm URL'lerin hreflang kümesini genişletmeli; sitemap'e yalnızca yeni dilin sayfaları eklenip eskiler güncellenmezse küme eksik kalıyor. Google bu durumda bazı dil versiyonları için sitemap'te göremediği hreflang ilişkilerini başka bir kaynaktan bulmaya çalışıyor; HTML head'de bu ilişkiler tutarlıysa sorun büyümüyor, ama iki katman birbirinden sapıyorsa hangisinin doğru olduğu belirsizleşiyor. Karar basit: route eşleme tablosu tek kaynak, sitemap fonksiyonu ve HTML head üretimi bu tablodan türetilmeli, ikinci bir veri kaynağına bağlı olmamalı.

Next.js'in i18n routing'inde ve Nuxt'un i18n modülünde görülen otomasyon eksikliğinin bir benzeri SvelteKit'te de geçerli: framework hiçbir noktada hreflang veya sitemap üretimini sizin için tamamlamıyor. Fark, SvelteKit'in bu sorumluluğu daha az gizlemesi; ekip baştan bunun elle kurulacağını bilerek başladığında, sonradan keşfedilen bir eksiklik riski büyük ölçüde ortadan kalkıyor.

SvelteKit'in çok dilli SEO tarafındaki asıl zorluğu, framework'ün az yardımcı olması değil, bu az yardımın ilk bakışta fark edilmemesi. Route yapısı çalışıyor, dil değiştirici görsel olarak doğru davranıyor, ama hreflang ve canonical katmanı hiç kurulmamış olabiliyor; çünkü bu katmanlar görsel bir hata üretmiyor, sadece Google'ın dil versiyonlarını doğru eşleştirememesine yol açıyor.

Route eşleme tablosunu tek gerçek kaynak haline getirip hreflang, canonical ve sitemap üretimini bu tablodan türetmek, SvelteKit projelerinde çok dilli SEO'yu sürdürülebilir kılan tek pratik disiplin. Bu disiplin kurulmadan eklenen her yeni dil veya yeni sayfa, mevcut tutarsızlıkların üzerine bir katman daha ekliyor. Tek tablo, tek üretim mantığı, tek doğrulama noktası. Proje büyüdükçe bu kural daha da geçerli hale geliyor.