Bot boş bir kabuk görüyor, çünkü istemci tarafında yönlendirme yapan bir uygulama her adrese aynı HTML dosyasını gönderiyor ve sayfayı sonradan JavaScript ile kuruyor. Google bu JavaScript'i çalıştırıyor ve sayfanın bitmiş hâlini görüyor. JavaScript'i hiç çalıştırmayan istemciler ise kabuğu geldiği gibi okuyor: her rotada aynı başlık, aynı önizleme etiketleri ve boş bir gövde.
Sahada bu durumun tek bir sorun gibi konuşulduğunu ve çoğu zaman "framework'ü değiştirin" cevabıyla kapatıldığını görüyoruz. Oysa SPA SEO sorunları üç ayrı yerden geliyor. Bir kısmı Google render ettiği hâlde sürüyor, bir kısmı yalnızca render etmeyen crawler'ları etkiliyor, bir kısmı da en sık önerilen çözümlerin kendisinden doğuyor. Hangisiyle uğraştığınızı bilmek, size yanlış çözüme harcanacak zamanı kazandırıyor.
SPA'da İlk HTML Neden Boş Gelir
İstemci tarafında yönlendirme yapan bir tek sayfa uygulamasında (single page application, SPA) sunucu, bir URL'nin hangi görünüme karşılık geldiğini bilmiyor. Tek bir giriş dosyası döndürüyor, genellikle index.html, ve neyin çizileceğine tarayıcıdaki router karar veriyor. Google'ın JavaScript SEO rehberi bu yapıya app shell modeli diyor.
Uygulama Kabuğu: Bir Kapsayıcı ve Bir Script Etiketi
Katı bir SPA'da view-source açtığınızda çoğunlukla bir <div id="app"></div> ve bir bundle bağlantısından fazlasını bulamazsınız. Next.js'in SPA rehberi aynı yapıyı tarif ediyor: tek bir HTML dosyası, her rota ve her veri isteği tarayıcıdaki JavaScript'e bırakılmış. Script etiketinin kendisi olağan bir şey, çünkü W3Techs Eylül 2026'da izlediği sitelerin %98,9'unda JavaScript sayıyor. SPA SEO açısından belirleyici olan, içeriğin yanıtın içinde gelip gelmediği.
Her Rota Aynı Head'i Taşır
Her URL aynı dosyayı döndürdüğü için her URL aynı <head> bölümünü de döndürüyor. İlk yanıttaki title, description, Open Graph etiketleri ve canonical, JavaScript onları yeniden yazana kadar /fiyatlar ile /blog/herhangi-bir-yazi adreslerinde birebir aynı kalıyor.
Google yeniden yazılan head'i render sonrasında okuyor, ama meta etiket dokümanı meta etiketleri mümkün olduğunca JavaScript ile değiştirmemenizi istiyor. Ortak head, şablondaki küçük kararları da site geneline yayıyor. Kabuktaki bir noindex bütün rotalara uygulanıyor. Kabukta sabit bir canonical dururken JavaScript ikincisini eklerse, sayfada birbiriyle çelişen iki etiket kalıyor.
Google Kabuğu Render Eder, Üç SPA Sorunu Yine Kalır
Google, 200 durum kodu dönen her sayfayı render queue'ya alıyor. Vercel ve MERJ'in nextjs.org üzerindeki 2024 çalışmasında tarama ile render'ın tamamlanması arasındaki medyan süre 10 saniyeydi. Render yine de HTML çekmekten pahalı bir iş ve bu fark, crawl budget'ın gerçek bir sınır olduğu büyük sitelerde hissediliyor.
İki durum render'ın işe yaramasını engelliyor. İlk HTML'de noindex varsa Google render'ı atlayabiliyor ve etiketi kaldıracak JavaScript hiç çalışmıyor. Google ayrıca yalnızca çekmesine izin verilen dosyaları render ediyor. Bu yüzden framework'ün script klasörünü robots.txt ile engellemek, istemci tarafında kurulan bir sayfayı render sonrasında da boş bırakıyor.
Hash URL'ler Google İçin Tek Adrestir
/#/urunler adresindeki bir görünüm Google için / ile aynı belge, çünkü # işaretinden sonrası sunucuya hiç gitmiyor. Google farklı içeriği fragment ile yüklememenizi istiyor ve bu yapıya dayanan AJAX crawling scheme'in 2015'ten beri kullanım dışı olduğunu hatırlatıyor. History API ile /urunler gibi gerçek yollar kullanmanızı ve sunucunun bu yollara doğrudan gelen istekleri de karşılamasını öneriyoruz.
href Taşımayan Linkler Takip Edilmeyebilir
Google bir linki, href özniteliği olan bir <a> elemanıysa güvenilir biçimde takip ediyor. JavaScript ile sonradan eklenen linkler de bu kurala dahil. Google'ın güvenilmez saydığı biçimler şunlar:
<a href="/urunler">Ürünler</a> <!-- taranabilir -->
<a routerLink="urunler/kategori">…</a> <!-- href yok -->
<span href="/urunler">…</span> <!-- <a> elemanı yok -->
<a onclick="goto('/urunler')">…</a> <!-- adres yalnızca olay işleyicide -->Google bu biçimleri yine de ayrıştırmayı deneyebileceğini söylüyor, ama bunu bir söz olarak okumamak gerekiyor. Son biçimi router bileşeni belirlediği için kontrolü render edilmiş HTML'de yapın.
Olmayan Her Adres 200 Döner: Soft 404
Hataları tarayıcıda yöneten bir SPA, ürünün olmadığını anladığında 200 kodunu çoktan göndermiş oluyor. Google bu sayfayı render ediyor, soft 404 olarak işaretleyebiliyor ve böyle hata sayfalarının indekse girebileceği konusunda uyarıyor. Google iki çıkış yolu veriyor: gerçek bir 404 dönen URL'ye JavaScript ile yönlendirmek ya da hata görünümüne JavaScript ile noindex eklemek. İkinci yol çalışıyor, çünkü etiket sayfa yüklendikten sonra ekleniyor ve kaldırılması gerekmiyor.
Boş Kabuğu Kim Okur: Render Etmeyen İstemciler
SPA'ya istek atan istemciler aynı davranmıyor. Bu yüzden "arama motorları JavaScript'i görmekte zorlanır" cümlesi bir karar vermek için fazla kaba kalıyor. Aşağıdaki tablo her istemciyi ilk HTML'de ne okuduğuna göre ayırıyor.
| İstemci | İlk HTML'de okuduğu | JavaScript çalıştırıyor mu | Dayanak |
|---|---|---|---|
| Kullanıcının tarayıcısı | Kabuk | Evet | Geçerli değil |
| Googlebot | Kabuk | Evet, 200 dönen her sayfada | Google Search Central |
| Bingbot | Kabuk | Evet; kritik içeriği client-side rendering arkasına saklamamanızı istiyor | Bing Webmaster Guidelines |
| Applebot | Kabuk | "May render" | Apple, About Applebot |
| Önizleme botları (Meta, Slack, LinkedIn) | Çekilen HTML'deki etiketler | Belgelenmemiş | Platform dokümanları |
| GPTBot, ClaudeBot, PerplexityBot | Yalnızca ilk HTML | Hayır (Aralık 2024'te gözlendi) | Vercel ve MERJ |
| Google AI Overviews, Gemini | Googlebot'un indeksi | Googlebot üzerinden | Google; Gemini render'ı için Vercel |
Paylaşım Önizlemeleri ve Open Graph Etiketleri
Önizleme botları Open Graph etiketlerini çektikleri HTML'den okuyor ve her platform bu çekmeyi sınırlıyor. Meta, etiketlerin yanıtın ilk 1 MB'ı içinde olmasını istiyor ve tarama birkaç saniyede bitmezse önizleme göstermiyor. Slack'in botu meta etiketleri okumak için sayfanın olabildiğince azını çekiyor, LinkedIn ise dört og: etiketini zorunlu tutuyor.
Bu platformların hiçbiri botunun JavaScript çalıştırıp çalıştırmadığını yazmıyor, o yüzden "hiç çalıştırmaz" demiyoruz. Her botun gerçekte hangi etiketleri okuduğunu Meta'nın Sharing Debugger'ı ve LinkedIn'in Post Inspector'ı gösteriyor. Bot ilk yanıtı okuyorsa, SPA'nızdan paylaşılan her link aynı başlık ve görselle önizleniyor.
AI Crawler'ları İlk Yanıtı Okur
Vercel ve MERJ, Aralık 2024'te büyük AI crawler'larının hiçbirinin JavaScript render etmediğini gözledi ve GPTBot ile ClaudeBot da bunların arasındaydı. Google tarafında AI Overviews için indekslenmiş ve snippet'e uygun olmanın ötesinde ek bir şart yok. Google'ın AI yüzeylerinde soru bu yüzden Googlebot'un render etmesine iniyor.
Bing, Kritik İçeriğin Client-Side Rendering Arkasına Saklanmamasını İstiyor
Bingbot sayfaları render ediyor, yine de Bing'in Webmaster Guidelines sayfası kritik içeriği client-side rendering arkasına saklamayı kaçınılacaklar arasında sayıyor. Güvenilir biçimde render edilemeyen içerik indekse ve Copilot'un grounding sonuçlarına giremeyebiliyor. DuckDuckGo geleneksel sonuçlarını büyük ölçüde Bing'den aldığı için aynı sınırın oraya da taşındığını düşünüyoruz.
Yaygın SPA Çözümleri Sorunu Çoğu Zaman Yalnızca Botlardan Saklar
SPA'lar için en sık önerilen çözümler aynı hamleyi yapıyor: botu tanıyıp ona başka bir şey vermek. Yayımlanmış iki geliştirici kurulumunu isim vermeden anlatıyoruz, çünkü burada bizi yazarlardan çok mekanizma ilgilendiriyor.
Botları User-Agent ile 301'lemek
Kurulumlardan biri .htaccess içinde crawler user-agent'larını yakalıyor ve onları 301 ile hafif bir /api/meta.php sayfasına gönderiyor. Google'ın yönlendirme dokümanı, yönlendirmeyi hedef URL'nin canonical olması gerektiğine dair bir sinyal olarak kullandığını yazıyor. Kuraldaki Google(.*) deseni Googlebot'u da yakaladığı için Google her ziyarette API adresine düşüyor, kullanıcı ise makalenin adresinde kalıyor. Bizim okumamıza göre kurulum bir önizleme sorununu bir canonical sorunuyla takas ediyor, ama bunu canlı bir sitede ölçmedik.
Yalnızca Botlara Prerender: Bayat Görüntü, Eksik Liste, Yanlış Dosya
İkinci kurulum listedeki her rotayı build sırasında HTML olarak kaydediyor ve nginx bu dosyaları yalnızca bot listesindeki user-agent'lara veriyor. Build anında üretilen HTML statik render anlamına geliyor ve Google bu yolu öneriyor. Aynı dosyaları yalnızca botlara sunmak ise yöntemi dynamic rendering'e çeviriyor, Google da bunu önerilen bir çözüm olarak görmüyor. Kodu okurken üç zayıf nokta bulduk:
- Bayat görüntü. Botlar build anındaki sürümde kalırken kullanıcılar güncel sürümü görüyor ve bu fark Google'ın "similar content" sınırını zorluyor.
- Elle tutulan bot listesi. Liste, Google'ın test araçlarının gönderdiği Google-InspectionTool ajanını kaçırıyor. Listenin yakaladığı Lighthouse işareti de 10.0.0 sürümünden beri user-agent'ta yer almıyor.
- Sunucunun bulamadığı dosya. /about rotası about.html olarak kaydediliyor, nginx kuralı ise /index.html dosyasına düşmeden önce yalnızca $uri ve $uri/ arıyor.
Böylece bot her rotada prerender edilmiş ana sayfayı 200 koduyla alıyor. nginx dokümanındaki örnek eksik parçayı ekliyor ve listede olmayan rotaların 404 dönmesini sağlıyor:
try_files $uri $uri/index.html $uri.html =404;Bu bulgular kodu ve nginx dokümanını okuyarak çıkardığımız sonuçlar. Kurulumu kendimiz çalıştırmadık.
Kalıcı Çözüm: İçerik ve Head Her Rotanın İlk HTML'inde
Tablodaki bütün istemcileri tek bir ilke karşılıyor: herkese aynı URL'de aynı HTML'i, rotanın kendi içeriği ve head etiketleriyle birlikte vermek. Google server-side rendering, statik render ya da hydration öneriyor ve gerekçelerinden biri, her botun JavaScript çalıştıramaması. Bu yapı kurulduğunda size ne bot listesi ne de yönlendirme gerekiyor.
Üç Yol: SSR, Statik Üretim ve Herkese Sunulan Prerender
Server-side rendering (SSR) HTML'i her istekte üretiyor. Static site generation (SSG), yani statik üretim, HTML'i istek gelmeden önce hazırlıyor ve web.dev'in Rendering on the Web makalesi ikisinin farklarını anlatıyor. Mevcut bir SPA'nın build sırasında alınan prerender'ı da aynı dosyalar her ziyaretçiye verildiği sürece statik üretim grubuna giriyor. İsteğe göre değişen sayfalar için SSR'ı, yalnızca yayınladığınızda değişen sayfalar için statik üretimi ya da prerender'ı öneriyoruz.
React ve Next.js'in Yönü
React ekibi 14 Şubat 2025'te Create React App'i yeni projeler için kullanım dışı bıraktı ve artık bir framework ile başlamayı öneriyor. Aynı duyuru, sunucu tarafı render'ın yalnızca SEO için olduğu fikrini yaygın bir yanlış anlama olarak niteliyor. Next.js App Router'da bir rotaya doğrudan gelen istek sunucuda üretilmiş HTML alıyor ve Googlebot her URL'yi bu şekilde istiyor.
Bir 'use client' direktifi bu HTML'i boşaltmıyor, çünkü Next.js client component'leri sunucuda prerender ediyor. İlk HTML'i boşaltan şey, içeriği tarayıcıda çekmek ya da ssr: false ile sunucu render'ını kapatmak. Sunucusuz barındırmada output: 'export' her rota için ayrı bir HTML dosyası üretiyor. Next.js dokümanları bunları hız üzerinden anlatıyor, arama tarafındaki sonuçlar ise bizim okumamız.
SPA SEO Sorunlarını Beş Adımda Test Etmek
Aşağıdaki her kontrol farklı bir istemcinin yerine geçiyor. Size önerimiz, beşini de ana sayfa dışındaki bir rotada çalıştırmanız:
- URL'de view-source açın ya da curl çalıştırın. Gelen yanıt, hiç render etmeyen bir istemcinin head etiketleriyle birlikte aldığı sürüm.
- Google Search Console'da URL Inspection'ı açın ve Googlebot'un ne çekip render ettiğini görmek için View crawled page seçin.
- Canlı testin Google-InspectionTool olarak geldiğini bilerek çalıştırın. İçeriği user-agent'a göre değiştiren bir sitede canlı test, Googlebot'un aldığından farklı olabilir.
- URL'yi Meta'nın Sharing Debugger'ına ve LinkedIn'in Post Inspector'ına verin ve her önizleme botunun okuduğu etiketlere bakın.
- Olmayan bir adresi curl -I ile isteyin ve durum kodunun 404 olduğunu doğrulayın.
Sayfadan tırnak içinde alınmış bir cümleyle yapılan site: araması, sonuç verirse iyi bir işaret. Boş sonuç ise hiçbir şey kanıtlamıyor, çünkü Google'ın site: dokümanı indekslenmiş URL'lerin bu aramada görüneceğini garanti etmiyor. Mobile-Friendly Test de artık kullanılamıyor, Google aracı 1 Aralık 2023'te kapattı. Bir rota ilk kontrolden geçtiği hâlde indekste yoksa, sıradaki teşhis taranmak ile indekslenmek arasındaki farkta başlıyor.
Sıkça Sorulan Sorular
SPA siteler Google'da indekslenir mi?
Evet. Google 200 durum kodu dönen SPA sayfalarını render ediyor ve render edilmiş içeriği indeksliyor. Hash tabanlı rotaları, href taşımayan linkleri ve 200 dönen hata görünümlerini yine de düzeltmeniz gerekiyor.
React ya da Vue SPA için SSR şart mı?
Google için şart sayılmaz, çünkü Google client-side içeriği render ediyor. Hiç render etmeyen istemciler ve ilk yanıtta rotaya özgü head etiketleri için ise sizin de SSR, statik üretim ya da herkese sunulan prerender kullanmanız gerekiyor.
SPA'da paylaşılan link neden hep aynı önizlemeyi gösteriyor?
Her rota aynı index.html dosyasını döndürdüğü için ilk yanıt her URL'de aynı Open Graph etiketlerini taşıyor. Meta'nın crawler'ının gerçekte hangi etiketleri okuduğunu Sharing Debugger gösteriyor.
Dynamic rendering SPA için hâlâ bir çözüm mü?
Hayır. Google'ın dokümanı dynamic rendering'i geçici bir çözüm olarak tanımlıyor ve karmaşıklık ile kaynak ihtiyacı yüzünden önermiyor. Aynı prerender edilmiş HTML'i her ziyaretçiye sunmak bu ihtiyacı ortadan kaldırıyor.
SPA'da Google Analytics 4 sayfa görüntülemelerini neden eksik sayıyor?
SPA'da rota değişikliği sayfayı yeniden yüklemediği için yüklemede tetiklenen bir etiket yalnızca ilk görüntülemeyi sayıyor. GA4'ün enhanced measurement ayarlarında page_view olayını tarayıcı geçmişi olaylarında da gönderen seçeneği açın. Bu seçenek pushState, popState ve replaceState olaylarını dinliyor.
Kaynaklar ve Referanslar
- Google Search Central, "Understand the JavaScript SEO basics", https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics (son güncelleme 4 Mart 2026; erişim 16 Eylül 2026)
- Google Search Central, "Fix Search-related JavaScript problems", https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript (son güncelleme 18 Aralık 2025; erişim 16 Eylül 2026)
- Google Search Central, "Meta tags and attributes that Google supports", https://developers.google.com/search/docs/crawling-indexing/special-tags (erişim 16 Eylül 2026)
- Google Search Central, "Link best practices for Google", https://developers.google.com/search/docs/crawling-indexing/links-crawlable (son güncelleme 10 Aralık 2025; erişim 16 Eylül 2026)
- Google Search Central, "Redirects and Google Search", https://developers.google.com/search/docs/crawling-indexing/301-redirects (son güncelleme 14 Nisan 2026; erişim 16 Eylül 2026)
- Google Search Central, "Dynamic rendering as a workaround", https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering (son güncelleme 10 Aralık 2025; erişim 16 Eylül 2026)
- Google Search Central, "AI features and your website", https://developers.google.com/search/docs/appearance/ai-features (erişim 16 Eylül 2026)
- Google Search Central, "site: search operator", https://developers.google.com/search/docs/monitor-debug/search-operators/all-search-site (erişim 16 Eylül 2026)
- Google Search Central Blog, "The Search Console mobile friendly testing tool (retired)", 1 Aralık 2023 güncelleme notu, https://developers.google.com/search/blog/2016/05/a-new-mobile-friendly-testing-tool (erişim 16 Eylül 2026)
- Google, "Google common crawlers", https://developers.google.com/crawling/docs/crawlers-fetchers/google-common-crawlers (son güncelleme 14 Temmuz 2026; erişim 16 Eylül 2026)
- Google Analytics Yardım, "[GA4] Enhanced measurement events", https://support.google.com/analytics/answer/9216061 (erişim 16 Eylül 2026)
- Bing Webmaster Tools, "Bing Webmaster Guidelines", https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a (erişim 16 Eylül 2026)
- DuckDuckGo Yardım, "Results sources", https://duckduckgo.com/duckduckgo-help-pages/results/sources/ (erişim 16 Eylül 2026)
- Apple, "About Applebot", https://support.apple.com/en-us/119829 (erişim 16 Eylül 2026)
- Meta for Developers, "Meta Web Crawlers", https://developers.facebook.com/docs/sharing/webmasters/web-crawlers (güncelleme 21 Mayıs 2026; erişim 16 Eylül 2026)
- LinkedIn Yardım, paylaşılan linklerde Open Graph etiketleri (a521928), https://www.linkedin.com/help/linkedin/answer/a521928 (erişim 16 Eylül 2026)
- Slack API dokümantasyonu, Slackbot robots sayfası, https://api.slack.com/robots (erişim 16 Eylül 2026)
- Giacomo Zecchini, Alice Alexandra Moore, Ryan Siddle ve Malte Ubl, "How Google handles JavaScript throughout the indexing process", Vercel, 31 Temmuz 2024, https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process (erişim 16 Eylül 2026)
- Vercel ve MERJ, "The rise of the AI crawler", Vercel, 17 Aralık 2024, https://vercel.com/blog/the-rise-of-the-ai-crawler (erişim 16 Eylül 2026)
- web.dev, "Rendering on the Web", son güncelleme 5 Ocak 2026, https://web.dev/articles/rendering-on-the-web (erişim 16 Eylül 2026)
- Matt Carroll ve Ricky Hanlon, "Sunsetting Create React App", React Blog, 14 Şubat 2025, https://react.dev/blog/2025/02/14/sunsetting-create-react-app (erişim 16 Eylül 2026)
- Next.js dokümantasyonu, "Server and Client Components", https://nextjs.org/docs/app/getting-started/server-and-client-components (son güncelleme 25 Ağustos 2026; erişim 16 Eylül 2026)
- Next.js dokümantasyonu, "Linking and Navigating", https://nextjs.org/docs/app/getting-started/linking-and-navigating (son güncelleme 25 Ağustos 2026; erişim 16 Eylül 2026)
- Next.js dokümantasyonu, "How to build single-page applications with Next.js", https://nextjs.org/docs/app/guides/single-page-applications (son güncelleme 25 Ağustos 2026; erişim 16 Eylül 2026)
- nginx, "Module ngx_http_core_module: try_files", https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files (erişim 16 Eylül 2026)
- Lighthouse değişiklik kaydı, 10.0.0 sürümü, 9 Şubat 2023, https://github.com/GoogleChrome/lighthouse/releases/tag/v10.0.0
- W3Techs, "Usage statistics of JavaScript as client-side programming language on websites", Eylül 2026, https://w3techs.com/technologies/details/cp-javascript (erişim 16 Eylül 2026)
