Astro'yu çok dilli bir proje için seçen ekiplerin çoğu aynı vaadin peşinde: statik üretimin hızı, JavaScript yükünün minimumda tutulması ve içerik ağırlıklı sitelerde performans bütçesinin baştan kazanılması. Bu vaat gerçek; Astro'nun varsayılan render modeli sayfayı build zamanında düz HTML'e çeviriyor ve istemciye giden JavaScript miktarını gerektiği kadar sınırlıyor. Ancak performans avantajı ile çok dilli SEO doğruluğu aynı şey değil. Astro'nun kendi i18n routing katmanı URL üretimini ve locale tespitini çözüyor; hreflang etiketlerinin doğruluğu, canonical kararı ve locale bazlı sayfa üretiminin eksiksizliği hâlâ geliştiricinin sorumluluğunda kalıyor.
Framework'ün v4 sürümüyle birlikte gelen yerleşik astro:i18n modülü, önceki manuel routing çözümlerine göre belirgin bir standartlaşma getirdi. Locale listesi, varsayılan dil ve URL öneki stratejisi artık tek bir konfigürasyon bloğunda tanımlanıyor. Ama bu modül de diğer framework'lerin i18n katmanlarıyla aynı sınırı taşıyor: routing'i standartlaştırıyor, SEO sinyallerini üretmiyor. Hreflang, canonical ve sitemap üretimi projeye özgü kod olarak eklenmek zorunda.
prefixDefaultLocale asimetrik URL üretir; hreflang kodu bu asimetriye özel bir dal yazmak zorundadır. Islands mimarisi sinyalleri statik HTML'de tutar, dil seçiciyi build zamanında props ile doldurursa çalışma zamanı API'sine ihtiyaç kalmaz. Risk render kuyruğunda değil build çıktısındadır: eksik çeviri klasörü, filtrelenmemiş getStaticPaths, tutarsız trailing slash.
Astro'nun i18n routing katmanı hangi kararı veriyor, hangisini geliştiriciye bırakıyor?
astro.config.mjs içindeki i18n bloğu üç temel alan içeriyor: locales dizisi, defaultLocale ve routing ayarı. routing.prefixDefaultLocale değeri false olduğunda varsayılan dil URL öneki almıyor; /urunler Türkçeyse /tr/urunler üretilmiyor. Bu davranış, Next.js'in varsayılan locale önek muafiyetiyle aynı sonucu doğuruyor: /urunler ile /en/urunler aynı içeriğin iki farklı sürümü olarak var oluyor ama aralarındaki hreflang ve canonical ilişkisi framework tarafından otomatik kurulmuyor.
Asimetrik URL yapısı, hreflang üretim koduna varsayılan locale için özel bir dal yazmayı gerektiriyor. Framework bunu çözmez. Bu özel dal büyüdükçe bakım yükü birikir ve varsayılan locale'nin önek alıp almadığı zamanla kodun farklı yerlerinde örtük bir varsayım haline gelir; yeni bir dil eklendiğinde veya URL yapısı değiştirildiğinde bu varsayım hata kaynağına dönüşür. Başlangıçta küçük görünen bu ayrıntı, ölçek artınca kendini gösterir.
routing.prefixDefaultLocale: true yapıldığında her locale tutarlı bir önek alıyor; bu, hreflang setini kurarken URL örüntüsünün her dilde birebir simetrik olmasını sağladığı için tercih edilmeli. Simetrik önek ayrıca sitemap alternate üretimini ve dil seçici URL hesaplamasını da aynı anda basitleştiriyor; URL kalıbı her locale'de birebir aynı örüntüyü izlediğinden bu katmanlarda da özel durum kodu yazma zorunluluğu ortadan kalkıyor.
Locale tespiti tarafında Astro, Astro.currentLocale değerini URL segmentinden okuyor; tarayıcı başlığına (Accept-Language) dayalı otomatik yönlendirme yerleşik olarak gelmiyor. Bu, çoğu framework'ün varsayılanına göre daha güvenli bir başlangıç noktası, çünkü Accept-Language tabanlı yönlendirmenin ürettiği tarama riski Astro projelerinde kendiliğinden oluşmuyor. Bazı ekipler kullanıcı deneyimi için middleware üzerinden bu tür bir yönlendirme ekliyor; yönlendirme eklendikten sonra risk projeye geri dönüyor ve yönlendirmenin bot trafiğini nasıl etkilediği ayrıca test edilmesi gerekiyor.
Hreflang etiketleri Astro'da kendiliğinden üretilmiyor
astro:i18n modülü locale listesini ve URL örüntüsünü bilse de, sayfa <head>'ine hreflang etiketi yazmıyor. Bu satır satır kod gerektiren bir adım. Genel yaklaşım, paylaşılan bir layout bileşeninde getRelativeLocaleUrl() fonksiyonunu her locale için çalıştırıp dönen URL'lerle hreflang link etiketlerini üretmek. Fonksiyon doğru URL'yi mevcut sayfa yoluna göre hesaplıyor; ama üretilen listenin hangi locale'leri içereceğine karar vermek geliştiricinin işi.
Statik içerik koleksiyonlarında (Astro Content Collections) her girişin hangi dillerde mevcut olduğu genellikle dosya yapısından çıkarılıyor: src/content/blog/tr/yazi.md ve src/content/blog/en/yazi.md gibi klasörleme, hangi çevirinin var olduğunu doğrudan gösteriyor. Bu, API tabanlı headless çözümlere göre daha şeffaf bir avantaj; headless CMS'lerde locale varlığını API'den sorgulama zorunluluğu burada dosya sisteminin kendisi tarafından karşılanıyor. Ancak bu şeffaflık otomatik doğrulama sağlamıyor; bir çeviri klasörü eksikse hreflang üretim kodu bunu fark etmek zorunda, aksi halde var olmayan bir URL'ye işaret eden kırık bir etiket yayınlanıyor.
Karşılıklı referans kuralı Astro'da da geçerliliğini koruyor: Türkçe sayfa İngilizce sürüme işaret ediyorsa İngilizce sürüm de Türkçeye işaret etmek zorunda. Content Collections'tan gelen veri her sayfa için aynı yardımcı fonksiyonu çağırdığından bu simetri genellikle kendiliğinden sağlanıyor; riskin oluştuğu nokta, bazı sayfaların (örneğin kampanya sayfaları) koleksiyon dışında elle oluşturulduğu durumlar. Bu tip sayfalar hreflang üretim akışına dahil edilmezse sessizce eksik kalıyor.
x-default değeri de aynı yardımcı fonksiyon setine eklenmek zorunda; Astro bunun hangi URL olması gerektiğine dair bir varsayım sunmuyor. x-default için hangi URL seçilmeli sorusu burada da proje kararı olarak kalıyor, genellikle varsayılan locale'in önek almayan sürümü bu rolü üstleniyor. Ancak prefixDefaultLocale: true seçildiğinde varsayılan locale de bir önek aldığından, x-default için önek almayan ayrı bir giriş sayfası tanımlanması gerekir; bu adım atlanırsa x-default hreflang setiyle çelişen bir URL'ye işaret eder ve sessiz bir sinyal tutarsızlığı oluşur.
getStaticPaths ve locale: hangi sayfalar statik üretiliyor?
Astro'nun dinamik rotalarında getStaticPaths() fonksiyonu, tıpkı Next.js'in Pages Router'daki eşdeğerinde olduğu gibi, build zamanında hangi path'lerin üretileceğini belirliyor. Çok dilli bir yapıda bu fonksiyonun dönen dizisi her locale için ayrı path nesneleri içermeli; params alanına locale ve slug birlikte yazılıyor.
Pratikte sık atlanan nokta şu: getStaticPaths içinde locale döngüsü kurulurken içerik koleksiyonu sorgusu locale bazlı filtrelenmezse, henüz çevirisi olmayan bir içerik için de path üretilmeye çalışılıyor ve build sırasında ya boş sayfa ya da hata oluşuyor. Doğru model, her locale için içerik koleksiyonunu ayrı sorgulamak ve yalnızca o locale'de gerçekten var olan girişler için path döndürmek. Kısmi çeviri senaryosunda bu ayrım kritik. Almanca henüz hazır olmayan bir yazı için Almanca URL üretilmemeli, dolayısıyla hreflang setinde de yer almamalı. Eksik çeviri için üretilen URL hem 404 riski taşır hem de hreflang setini kırık bırakır; sessiz bir build hatası, canlıda Googlebot'un karşılaşacağı bir sorun olarak kalır.
Astro varsayılan olarak output: 'static' modunda çalışıyor; bu modda tüm sayfalar build zamanında üretiliyor ve sonuç düz HTML dosyaları olarak sunucuya veya CDN'e yükleniyor. SSR adaptörü (Node, Vercel, Netlify gibi) eklendiğinde output: 'hybrid' veya 'server' seçilebiliyor; bu durumda bazı sayfalar istek anında render edilebiliyor. Çok dilli bir proje için statik mod, hreflang ve canonical değerlerinin build zamanında sabitlenmesi anlamına geliyor; bu, Googlebot'un render kuyruğunda yaşadığı gecikme riskini büyük ölçüde ortadan kaldırıyor çünkü Googlebot'un gördüğü HTML zaten nihai hâl, ayrıca bir JavaScript çalıştırma aşamasına ihtiyaç yok.
Astro'da canonical URL: self-referencing kural ve locale tutarlılığı
Astro'da canonical etiketi otomatik üretilmiyor; her sayfa şablonunda Astro.url üzerinden mevcut tam URL okunup <link rel="canonical"> olarak yazılmak zorunda. Çok dilli bir yapıda doğru varsayılan, her locale sürümünün kendi URL'sini kendi canonical'ı olarak göstermesi; yani /en/urunler sayfası /en/urunler'i canonical gösteriyor, /urunler'i değil. Çok dilli sitelerde canonical yönetiminin temel ilkesi Astro projelerinde de aynı şekilde geçerli; her dil sürümü kendi kimliğini taşımalı, canonical zinciri diller arasında birleştirilmemeli.
Query parametresi taşıyan sayfalarda (filtre, sıralama, sayfalama) canonical kararı locale bilgisinden bağımsız ayrıca ele alınmalı. Astro'nun Astro.url.searchParams üzerinden gelen değerler canonical hesaplamasına dahil edilmemeli; aksi halde her filtre kombinasyonu için farklı bir canonical URL üretilir ve bu, dil sürümü sayısıyla çarpılarak URL patlamasına dönüşür.
Trailing slash davranışı da gözden kaçan bir detay. Astro'nun trailingSlash ayarı ('always', 'never', 'ignore') canonical URL'nin sonunda eğik çizgi olup olmamasını belirliyor; bu ayar tüm locale'lerde tutarlı olmalı. Tutarsızlık ince ama somut bir risk. İspanyolca sürümde /es/urunler/ canonical gösterilirken İngilizce sürümde /en/urunler gösterildiğinde, hreflang setinde referans verilen URL'lerle canonical birbirini doğrulamıyor. Build konfigürasyonundaki tek bir satır tutarsızlığı tüm locale'lerde hatalı canonical üretimine yol açabiliyor.
Islands mimarisi ve partial hydration: çok dilli SEO'ya etkisi
Astro'nun ayırt edici özelliği islands mimarisi: sayfa varsayılan olarak statik HTML olarak gönderiliyor, interaktif bileşenler (client:load, client:visible gibi direktiflerle işaretlenenler) yalnızca ihtiyaç anında hydrate ediliyor. Bu model çok dilli SEO açısından doğrudan bir avantaj sağlıyor: hreflang, canonical ve lang attribute'u statik HTML'in bir parçası olduğu için Googlebot'un bu sinyalleri görmesi JavaScript çalıştırmasına bağlı değil.
Dil değiştirici (language switcher) gibi bileşenler genellikle interaktif; kullanıcının seçtiği dile göre URL üretmesi gerekiyor. Bu bileşen client:visible ile hydrate ediliyorsa ve gerekli locale URL listesini kendi içinde bir API çağrısıyla topluyorsa, bu çağrı başarısız olduğunda dil değiştirici görünür ama tıklanamaz hâle gelebiliyor; bu bir SEO sorunu değil ama kullanıcı deneyimi sorunu, ki kullanıcı deneyimi sinyalleri dolaylı olarak sıralamaya yansıyor. Daha güvenli yaklaşım, locale URL listesini build zamanında props olarak bileşene geçmek; bu şekilde çalışma zamanında herhangi bir veri çekme işlemi gerekmiyor ve bileşen anında kullanılabilir durumda geliyor. Ek avantajı, props olarak geçilen URL listesinin statik HTML'de görünür olması, yani Googlebot'un dil seçicinin işaret ettiği URL'leri kaynak kodda okuyabilmesidir.
SSR adaptörü kullanılan hibrit projelerde bazı sayfalar istek anında render edilirken bazı sayfalar build zamanında statik kalabiliyor. Bu karışık modelde, hangi sayfaların hangi render stratejisini kullandığını locale bazında ayrı ayrı izlemek gerekiyor; bir dilin ürün sayfaları statik, başka bir dilin (örneğin henüz stabilize olmamış bir pazar için) aynı sayfalar SSR ise, iki dil sürümü arasında render gecikmesi farkı oluşur ve bu fark Google'ın iki sürümü farklı hızda indekslemesine yol açabilir.
@astrojs/sitemap entegrasyonu ve build çıktısında doğrulama
Resmi @astrojs/sitemap entegrasyonu, i18n konfigürasyonu tanımlıysa locale bilgisini otomatik olarak okuyup sitemap'e xhtml:link alternate girişleri ekleyebiliyor; bu, uluslararası sitemap katmanının Astro'daki karşılığıdır; bu, hreflang ilişkilerinin sitemap üzerinden taşınması gerektiğinde manuel XML üretimine göre somut bir kısayol. Ancak entegrasyonun otomatik ürettiği alternate seti, yalnızca getStaticPaths ile fiilen üretilmiş sayfaları kapsıyor; içerik koleksiyonunda tanımlı olup path listesine eklenmemiş bir çeviri, sitemap'te de görünmüyor. Sitemap doğruluğu, kaynağında path üretim mantığının doğruluğuna bağlı kalıyor; sitemap kendi başına bir güvence katmanı değil, önceki adımların doğru çalıştığının bir yansıması.
Build çıktısı statik dosyalar olduğu için yayın öncesi doğrulama Astro'da nispeten basit. astro build sonrası dist/ klasöründeki HTML dosyaları doğrudan açılıp hreflang, canonical ve lang attribute değerleri kaynak koddan okunabiliyor. Bu, SSR tabanlı framework'lerde çalışma zamanı davranışını simüle etmek zorunda kalmaktan daha güvenilir bir kontrol noktası; üretilen dosya zaten Googlebot'un göreceği son hâl. Birkaç locale ve sayfa tipi kombinasyonunu build çıktısında elle karşılaştırmak, canlıya almadan önce en ucuz doğrulama adımı.
Statik dosyalar bir CDN üzerinden dağıtılıyorsa, CDN'in locale bazlı cache anahtarlarını nasıl ürettiği ayrıca kontrol edilmeli. Bazı CDN yapılandırmaları Accept-Language başlığına göre farklı içerik döndürüyormuş gibi davranıyor; oysa Astro'nun statik çıktısında her locale zaten kendi URL'sinde ayrı bir dosya. Cache anahtarının URL'e göre değil başlığa göre kurulduğu bir senaryoda, bir kullanıcının tarayıcı dili farklı bir locale'in HTML'ini önbellekten alabiliyor. Bu Astro'nun kendi hatası değil; dağıtım katmanının statik model ile uyumsuz yapılandırılmasının sonucu. Bunu erken yakalamak için, bir CDN önbellekleme kuralı değiştirildiğinde hreflang başlıklarını ve dönen içeriği locale bazında doğrulamak standart bir kontrol adımı olmalı.
Astro'nun sunduğu gerçek kazanç, çok dilli SEO sinyallerinin doğru üretilmesi koşuluyla, bu sinyallerin Googlebot'a JavaScript bağımlılığı olmadan ulaşmasını garanti etmesi. Framework build zamanında her şeyi sabitlediği için çalışma zamanı sürprizleri (render gecikmesi, hydration hatası, API çağrısı başarısızlığı) SEO açısından kritik alanlardan büyük ölçüde çıkarılıyor.
Riski tamamen ortadan kaldırmıyor, yeniden konumlandırıyor. Hata artık çalışma zamanında değil, build zamanında ve içerik modelinde oluşuyor: eksik bir çeviri klasörü, filtrelenmemiş bir getStaticPaths sorgusu veya tutarsız bir trailingSlash ayarı, statik üretimin garantisi altında sessizce yayına çıkabiliyor. Statik modelin avantajından tam olarak yararlanmak, bu kontrol noktalarını build sürecinin bir parçası hâline getirmekten geçiyor.