SPA SEO Sorunları: Bot Sayfanızda Neden Boş Kabuk Görüyor?
SEO

SPA SEO Sorunları: Bot Sayfanızda Neden Boş Kabuk Görüyor?

Tek sayfa uygulaması (SPA), her URL için neredeyse boş aynı index.html dosyasını döndürür. Googlebot bu kabuğu render eder, ama Aralık 2024'te ChatGPT'nin crawler'ı isteklerinin %11,50'sinde JavaScript çekip hiçbirini çalıştırmadı. SPA SEO sorunlarının çözümü ilk HTML'dedir.

Sercan Gökpınar
10 dk

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ğuJavaScript çalıştırıyor muDayanak
Kullanıcının tarayıcısıKabukEvetGeçerli değil
GooglebotKabukEvet, 200 dönen her sayfadaGoogle Search Central
BingbotKabukEvet; kritik içeriği client-side rendering arkasına saklamamanızı istiyorBing Webmaster Guidelines
ApplebotKabuk"May render"Apple, About Applebot
Önizleme botları (Meta, Slack, LinkedIn)Çekilen HTML'deki etiketlerBelgelenmemişPlatform dokümanları
GPTBot, ClaudeBot, PerplexityBotYalnızca ilk HTMLHayır (Aralık 2024'te gözlendi)Vercel ve MERJ
Google AI Overviews, GeminiGooglebot'un indeksiGooglebot üzerindenGoogle; Gemini render'ı için Vercel
Tek sayfa uygulamasında yedi istemcinin ilk HTML'de ne okuduğunu ve JavaScript çalıştırıp çalıştırmadığını gösteren tablo. Kullanıcının tarayıcısı, Googlebot ve Bingbot kabuğu okuyup JavaScript çalıştırıyor; Applebot için Apple 'May render' diyor. Meta, Slack ve LinkedIn önizleme botları çekilen HTML'deki etiketleri okuyor ve JavaScript davranışları belgelenmemiş. GPTBot, ClaudeBot ve PerplexityBot yalnızca ilk HTML'i okuyor ve Aralık 2024'te JavaScript çalıştırmadıkları gözlendi. Google AI Overviews ve Gemini, Googlebot'un indeksine dayanıyor.
Tek sayfa uygulamasında yedi istemcinin ilk HTML'de ne okuduğunu ve JavaScript çalıştırıp çalıştırmadığını gösteren tablo. Kullanıcının tarayıcısı, Googlebot ve Bingbot kabuğu okuyup JavaScript çalıştırıyor; Applebot için Apple 'May render' diyor. Meta, Slack ve LinkedIn önizleme botları çekilen HTML'deki etiketleri okuyor ve JavaScript davranışları belgelenmemiş. GPTBot, ClaudeBot ve PerplexityBot yalnızca ilk HTML'i okuyor ve Aralık 2024'te JavaScript çalıştırmadıkları gözlendi. Google AI Overviews ve Gemini, Googlebot'un indeksine dayanıyor.

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.
nginx'te yalnızca botlara prerender sunan bir kurulumun sequence diyagramı. Bot olarak eşleşen bir crawler /about ister, nginx root'u /prerendered yapar, /about ve /about/ dosyalarını bulamaz ve son parametre olan /index.html'e düşer. Crawler'a prerender edilmiş ana sayfanın title, meta etiketleri ve gövdesi 200 OK ile döner, çünkü kural about.html için $uri.html'i hiç denemez. Düzeltme kutusu nginx dokümanındaki try_files $uri $uri/index.html $uri.html =404 örneğini veriyor. Diyagram Beaked'in yayımlanmış kodu okumasından çıkan bir analiz, canlı sitede test edilmedi.
nginx'te yalnızca botlara prerender sunan bir kurulumun sequence diyagramı. Bot olarak eşleşen bir crawler /about ister, nginx root'u /prerendered yapar, /about ve /about/ dosyalarını bulamaz ve son parametre olan /index.html'e düşer. Crawler'a prerender edilmiş ana sayfanın title, meta etiketleri ve gövdesi 200 OK ile döner, çünkü kural about.html için $uri.html'i hiç denemez. Düzeltme kutusu nginx dokümanındaki try_files $uri $uri/index.html $uri.html =404 örneğini veriyor. Diyagram Beaked'in yayımlanmış kodu okumasından çıkan bir analiz, canlı sitede test edilmedi.

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:

  1. 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.
  2. 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.
  3. 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.
  4. URL'yi Meta'nın Sharing Debugger'ına ve LinkedIn'in Post Inspector'ına verin ve her önizleme botunun okuduğu etiketlere bakın.
  5. 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

Beaked

Gerçek gelir artıran veriye dayalı SEO, e-ticaret pazarlaması, performans pazarlaması ve potansiyel müşteri edinimi. Her kampanya ölçülebilir bir geri dönüş sağlar ve bütçe, dönüşüm getiren alanlara aktarılır. GEO, yapay zeka destekli aramalarda markanızın görünür kalmasını sağlar.

Fatih Sultan Mehmet Mah. Balkan Cad. Meydan İstanbul AVM NO: 62/A Ümraniye / İstanbul

© 2026 Beaked Agency. All rights reserved.