Strapi'nin internationalization eklentisi kurulum ekranında tek bir anahtar gibi görünür: açarsınız, birkaç locale eklersiniz, içerik modeliniz artık "çok dilli" olur. Next.js tarafında da benzer bir yanılsama var; [locale] segmentini route yapısına eklemek, i18n kutusunu işaretlemiş gibi hissettirir. Gerçekte bu iki sistemin birleşimi, hreflang ve canonical kararlarını hangi tarafın vereceği netleşmeden bırakılırsa, üç ayrı yerde çelişen bir çok dilli yapı üretir: Strapi'nin ilişki modeli, Next.js'in statik üretim mantığı ve tarayıcıya giden son HTML.
Bu kombinasyonun kendine özgü riski, iki sistemin de kendi başına "doğru" çalışmasıdır. Strapi API'si istediğiniz locale için doğru veriyi döner, Next.js de o veriyi doğru şekilde render eder. Sorun aradaki köprüde oluşur: hangi locale'lerin birbirine gerçekten bağlı olduğunu, hangi içeriğin henüz çevrilmediğini ve bir sayfa güncellenince diğer dil sürümünün ne zaman tazeleneceğini kimse otomatik olarak çözmez.
Aşağıda bu köprünün beş karar noktası ve bir işletim sorunu ele alınıyor: locale ilişkilerinin Strapi'de nasıl kurulduğu, bu ilişkinin Next.js route yapısına nasıl taşındığı, hreflang'ın hangi veriden üretildiği, eksik çevirilerde ne olacağı, canonical'ın hangi URL'i işaret edeceği ve içerik güncellenince statik sayfaların nasıl tazeleneceği.
Strapi'de locale mimarisi: internationalization plugin ne sağlar?
Strapi'nin i18n eklentisi, locale desteğini içerik tipi bazında açmanızı ister; bir koleksiyonu (örneğin "Blog Post") i18n'e açtığınızda, o tip için her locale ayrı bir entry olarak var olur. Bu entry'ler birbirine otomatik bağlanmaz. Bir editör Türkçe yazıyı oluşturduktan sonra "Add locale" ile İngilizce sürümü açtığında, Strapi bu iki entry'yi localizations ilişkisiyle birbirine bağlar; ama editör yeni bir kayıt oluşturup içeriği elle kopyalarsa, bu ilişki kurulmaz ve iki dil sürümü birbirinden habersiz iki ayrı kayıt olarak kalır.
Bu ayrım kritik, çünkü hreflang üretiminin tamamı bu localizations ilişkisine dayanıyor. İlişki yoksa, o dilin var olduğunu API çıktısından tespit etmenin pratik bir yolu kalmaz. Bir editöryal ekip büyüdükçe, "her zaman Add Locale ile çevir" kuralı yazılı bir standart olmadan kendiliğinden uygulanmaz; bu yüzden içerik modelinin bir parçası olarak editör eğitimi de teknik kurulumun kadar önemli hale gelir.
Strapi'de ayrıca bir "default locale" ayarı vardır ve bu ayar, API isteğinde locale parametresi belirtilmediğinde hangi içeriğin döneceğini belirler. Bu varsayılan, hreflang'ın x-default değeriyle otomatik eşleşmez; ikisi ayrı kararlardır ve birini diğerinden çıkarsamak hataya açıktır. Default locale Türkçe olabilir, ama x-default için İngilizce sürümü tercih etmek isteyebilirsiniz, özellikle kaynak dil pazarı küçükse.
Content-type builder ekranında i18n'i açtıktan sonra bir de "locale bazında alan" ayrımı gelir: bazı alanların (örneğin görsel veya SKU kodu) her locale'de aynı kalması, bazılarının (başlık, açıklama, slug) her locale'de bağımsız değer taşıması gerekir. Strapi bu ayrımı alan düzeyinde localized: true/false seçeneğiyle yapar; bir görsel alanı yanlışlıkla localized işaretlenirse, editör her dil için aynı görseli tekrar tekrar yüklemek zorunda kalır ve bir dilde güncellenen görsel diğerlerine yansımaz. Bu küçük yapılandırma hatası, içerik modelinin büyümesiyle birlikte fark edilmesi güçleşen bir bakım yüküne dönüşür.
Next.js tarafında route yapısı: Strapi locale'leri App Router'a nasıl taşınır?
App Router'da app/[locale]/... yapısı, statik üretim için generateStaticParams fonksiyonunun Strapi'den locale listesini çekmesini gerektirir. Burada sık yapılan bir kısayol, locale listesini kod içine sabit dizi olarak yazmaktır; bu, Strapi admin panelinden yeni bir dil eklendiğinde kod tarafında da manuel bir güncelleme gerektirir ve iki sistem arasında senkronizasyon kaybı riski taşır. Locale listesini build zamanında Strapi'nin /api/i18n/locales uç noktasından çekmek, bu kaymayı ortadan kaldırır.
İkinci karar, locale tespitinin nerede yapılacağıdır. Middleware katmanında Accept-Language başlığına göre otomatik yönlendirme kurmak kullanıcı deneyimini iyileştirir gibi görünür, ama bu yönlendirmenin SEO'ya nasıl yansıdığı ayrı bir problemdir; Googlebot'un bu başlığı nasıl gönderdiği ve redirect zincirinin canonical'ı nasıl etkilediği Accept-Language yönlendirmesinin SEO'yu bozup bozmadığını ele alan yazıda ayrıntılı işleniyor; burada asıl kural, bu yönlendirmenin yalnızca kök URL'de (/) çalışması ve dile özgü içerik sayfalarında hiç devreye girmemesidir.
Üçüncü nokta, Strapi'den gelen slug alanının locale'e göre farklı olabilmesidir. Bir editör İngilizce sürümde slug'ı "hreflang-guide" olarak yazarken Türkçe sürümde "hreflang-rehberi" yazabilir; bu, URL yapısı açısından doğru bir tercih olsa da, iki slug'ı birbirine eşleyecek bir mekanizma gerektirir. generateStaticParams her locale için kendi slug listesini üretirken, aynı içeriğin farklı dillerdeki karşılığını bulan bir eşleme tablosu (genellikle Strapi entry'sinin documentId veya benzeri sabit kimliği üzerinden) route metadata'sında taşınmalıdır; aksi halde dil değiştirme bağlantısı her zaman ana sayfaya döner.
Bu eşleme tablosunun pratik yansıması dil seçici bileşenidir. Kullanıcı bir Türkçe sayfadan İngilizce sürüme geçmek istediğinde, buton onu doğrudan o içeriğin İngilizce karşılığına götürmelidir; ana sayfaya veya İngilizce sürümün genel bloguna yönlendirmek kullanıcı deneyimini bozar ve dolaylı olarak bir SEO sinyali de kaybettirir, çünkü kullanıcı istediği sayfaya ulaşamadığında hemen geri dönme olasılığı yükselir. Bu eşlemenin doğru çalışıp çalışmadığını test etmenin en hızlı yolu, yayındaki her dil çiftinde birkaç örnek sayfa üzerinden dil değiştirme bağlantısını elle takip etmektir; otomasyon kurulana kadar bu manuel kontrol atlanmamalıdır.
Hreflang üretimi: Strapi API'sinden statik meta'ya kadar veri akışı
Next.js'in generateMetadata fonksiyonu, alternates.languages alanına bir nesne vererek hreflang etiketlerini üretir. Bu nesnenin içeriği doğrudan Strapi'nin populate=localizations parametresiyle döndürdüğü ilişkiden gelir. Pratikte akış şu şekilde işler: sayfa isteği geldiğinde entry'nin locale ilişkileri çekilir, her ilişkili locale için slug ve locale kodu okunur, bu liste alternates.languages nesnesine dönüştürülür.
Bu akışın kırılma noktası, populate parametresinin unutulmasıdır. Strapi varsayılan olarak ilişkileri populate etmez; bir geliştirici performans optimizasyonu yaparken populate=localizations'ı sorgudan çıkarırsa, sayfa görünüşte hatasız render olur ama hreflang etiketleri sessizce kaybolur. Bu tür bir regresyon genellikle derleme hatası vermediği için fark edilmesi haftalar sürebilir; build sonrası bir kontrol adımı olarak üretilen HTML'de hreflang etiketi sayısının locale sayısıyla eşleşip eşleşmediğini otomatik test etmek, bu riski erken yakalar.
İkinci kırılma noktası, hreflang kodunun formatıdır. Strapi'de locale kodları genellikle tr, en, de gibi kısa ISO 639-1 formatında tutulur; ama hedef pazar bölgeye özgüyse (İngiltere ile ABD İngilizcesi ayrımı gibi) bu kısa kod yeterli hedeflemeyi sağlamaz. Strapi'nin locale ayarları BCP 47 formatında en-GB, en-US gibi kodları desteklese de, bu kodların hreflang etiketine aynen taşınması gerekir; dönüşüm sırasında bir kısaltma veya normalize işlemi eklenirse, hreflang seti sessizce yanlış hedeflemeye kayar.
Bu akışı kurduktan sonra genel hreflang kurallarının (karşılıklı referans, x-default, self-referencing) hâlâ geçerli olduğunu unutmamak gerekir; Strapi ve Next.js sadece üretim mekanizmasını değiştirir, hreflang'ın temel kurallarını anlatan yazıda geçen ilkelerin hiçbiri ortadan kalkmaz.
Bu üretim zincirini yayına almadan önce doğrulamak için tek bir sayfayı elle incelemek yeterli değildir; build çıktısındaki tüm HTML dosyaları üzerinde basit bir script çalıştırıp her sayfadaki hreflang etiket sayısını, o içeriğin Strapi'deki localizations ilişkisinin uzunluğuyla karşılaştırmak, eksik populate veya kırık ilişki durumlarını yayın öncesi yakalar. Bu kontrol, CI hattına bir adım olarak eklendiğinde, hreflang regresyonunun canlıya sızma riskini büyük ölçüde azaltır.
Yayın durumu dile göre değişince: taslak, henüz çevrilmemiş içerik ve fallback kararı
Strapi'nin draft ve publish sistemi locale bazında bağımsız çalışır: bir entry'nin Türkçe sürümü yayında olabilirken İngilizce sürümü hâlâ taslak halinde kalabilir. Bu durumda Next.js tarafında üç seçenek vardır ve hangisinin seçildiği açıkça karar verilmesi gereken bir noktadır, kendiliğinden doğru olan bir varsayılan yoktur.
Birinci seçenek, taslak locale için 404 döndürmektir; bu en temiz seçenektir çünkü var olmayan bir sayfa gibi davranır ve hreflang setinde de bu locale'e yer verilmez. İkinci seçenek, taslak içeriği görünmez tutup dil seçicide o dili hiç listelememektir; bu, birinciyle aynı sonucu üretir ama kullanıcı arayüzü tarafında ek bir filtre gerektirir. Üçüncü ve riskli seçenek, İngilizce URL'de Türkçe içeriği fallback olarak göstermektir; bu yaklaşım kullanıcı deneyimini kurtarır gibi görünse de, aynı içeriğin iki farklı URL'de göründüğü bir kopya içerik senaryosu yaratır ve hreflang o URL için gerçekte var olmayan bir dil sürümünü işaret etmiş olur.
Üçüncü seçenek özellikle büyük katalog sitelerinde cazip gelir, çünkü çeviri süreci tamamlanana kadar "boş" sayfa göstermemek ister. Ama bu tercih yapılacaksa, fallback gösterilen sayfanın noindex ile işaretlenmesi ve o locale için hreflang'a hiç girilmemesi gerekir; aksi halde Google'ın aynı içeriği farklı dillerde duplicate olarak işaretlemesi olası hale gelir, bu senaryonun teknik arka planı aynı içeriğin farklı dillerde duplicate işaretlenmesini ele alan yazıda ayrıntılı işleniyor.
Canonical ve locale parametresi: Next.js'te hangi URL yetkili?
Self-canonical kuralı burada da geçerlidir: her locale kendi URL'ine canonical vermelidir, ana dile veya varsayılan locale'e değil. Next.js'te bu, generateMetadata içinde alternates.canonical alanının dinamik olarak o sayfanın kendi locale ve slug'ından üretilmesi anlamına gelir; statik bir canonical değeri (örneğin build zamanında sabitlenmiş kök domain) tüm locale'lerde aynı URL'e işaret eden bir hataya dönüşebilir.
Bir başka risk, locale'in URL path'inde değil query parametresinde taşınmasıdır (?lang=en gibi). Bu yapı Next.js App Router'ın [locale] segment mantığıyla doğal olarak uyuşmaz, ama bazı ekipler geçiş dönemlerinde veya eski bir yapıdan taşınırken bu deseni miras alır. Query parametresiyle locale ayrımı yapılan bir sistemde canonical genellikle parametreyi görmezden gelir ve tüm dil sürümleri aynı canonical URL'e toplanır; bu, hreflang setinin işaret ettiği URL'lerle canonical'ın çelişmesine yol açar.
Varsayılan locale'in URL'de önek taşıyıp taşımaması da (/tr/sayfa ile /sayfa arasındaki fark) canonical ve sitemap üretiminde tutarlı olmalıdır. Next.js'in localePrefix ayarı "as-needed" seçeneğiyle varsayılan dili öneksiz bırakabilir; bu durumda sitemap üretim scripti de aynı mantığı izlemeli, aksi halde sitemap'te var olmayan bir /tr/ önekli URL listelenir ve Google bu URL'i taradığında 404 veya redirect ile karşılaşır.
Önizleme modu ve webhook: içerik güncellenince statik sayfalar nasıl tazelenir?
Strapi'de bir editör içeriği güncelleyip yayınladığında, Next.js'in statik olarak ürettiği sayfa bunu otomatik bilmez; bu bağlantı bir webhook üzerinden kurulur. Strapi'nin "afterUpdate" veya "afterPublish" lifecycle olayı, Next.js'in revalidatePath veya revalidateTag uç noktasını tetikler. Buradaki sık atlanan ayrıntı, webhook payload'ının hangi locale'de değişiklik olduğunu taşıyıp taşımadığıdır; taşımıyorsa, revalidation isteği hangi path'in tazeleneceğini bilemez ve genellikle sadece varsayılan locale sayfası yenilenir.
Bu boşluk, bir editör İngilizce sürümü güncellediğinde Türkçe sayfanın (doğru şekilde) değişmeden kalması, ama İngilizce sayfanın da (yanlışlıkla) eski haliyle görünmeye devam etmesi şeklinde ortaya çıkar. Çözüm, webhook'un Strapi entry'sindeki locale alanını payload'a dahil etmesi ve Next.js tarafındaki revalidation fonksiyonunun bu locale bilgisini path'e (/en/blog/slug gibi) yansıtmasıdır.
Önizleme modu (draft preview) ayrı bir katman ekler: bir editör henüz yayınlanmamış bir çeviriyi önizlemek istediğinde, Next.js'in preview route'u Strapi'nin draft API'sine bağlanmalı ve bu önizleme çıktısına noindex eklenmelidir; aksi halde önizleme URL'i bir şekilde dışarı sızarsa (paylaşılan bir link üzerinden), henüz tamamlanmamış bir çeviri arama sonuçlarına düşme riski taşır. Bu risk, render mimarisinin genel olarak taşıdığı gecikme ve tutarsızlık sorunlarıyla aynı aileden gelir; JavaScript ile render edilen çok dilli içeriğin Google tarafından nasıl görüldüğünü ele alan yazıda bu ailenin diğer üyeleri de ele alınıyor.
Strapi ve Next.js'in birlikte kullanımı, çok dilli SEO kararlarını ortadan kaldırmaz; sadece bu kararların nerede alınacağını değiştirir. Locale ilişkisi editör panelinde kurulur, hreflang üretimi API sorgusunda şekillenir, canonical route metadata'sında sabitlenir ve tazelik webhook konfigürasyonunda garanti edilir. Bu dört katmandan biri gözden kaçarsa, diğer üçü teknik olarak doğru çalışsa bile sonuç aynı ölçüde bozuk olur.
Yeni bir dil eklerken veya mevcut kurulumu denetlerken bu dört katmanı sırayla kontrol etmek, hangi aşamada bir kopukluk olduğunu hızla ortaya çıkarır. Sorun genellikle tek bir büyük hatada değil, birbirini besleyen küçük varsayımlarda birikir; her katmanın kendi başına test edilmesi, bu birikimi büyümeden yakalamanın en pratik yoludur.