Teknik SEO Denetimi: Screaming Frog, Lighthouse SEO Audit'leri, Bing Site Scan, URL Inspection API ve CI'da Kendi Crawler'ın
Lighthouse'un SEO puanı tek bir sayfanın temel kurallara uyduğunu gösterir; kırık link, orphan sayfa ve hreflang dönüşü gibi site düzeyindeki sorunları ancak siteyi gezen bir crawler bulur. Screaming Frog, Bing Site Scan ve URL Inspection API'nin neyi gördüğünü karşılaştırıyor, sitemap ve link graph'ı kontrol eden kendi C# crawler'ımızı yazıp GitHub Actions'ta her PR'da çalıştırıyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 (ASP.NET Core 10.0.15) ve AngleSharp 1.10.3 ile derlenip denenmiştir; Lighthouse 13.6.0 Playwright'ın Chromium'uyla çalıştırılmıştır. Screaming Frog, Bing Site Scan ve URL Inspection API bölümleri bu araçların Kasım 2026'da okunan resmî dokümanlarına dayanır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) katalog sayfası, bir önceki yazının taslak kodunda Lighthouse'un SEO kategorisinden 100 alıyor. Aynı taslakta sayfada dört filtre link'i var ve dördü de 404 dönen bir adrese gidiyor.
Lighthouse bunu neden görmedi? Ve bir sitenin teknik SEO sağlığını sayfa sayfa değil bütün olarak kim, nasıl kontrol eder?
Bu yazı Teknik SEO serisinin son yazısı. Önceki on beş yazıda kurduğumuz kuralların (status code, redirect, canonical, hreflang, sitemap, robots.txt, görsel, video, filtre URL'leri) bir sonraki deploy'da bozulmadığını görmenin yolu bir denetim. Hangi aracın neyi gördüğüne bakıp CI'da çalışan kendi crawler'ımızı yazacağız.
Denetim Listesi ve Araçların Görüş Alanı
Bir teknik SEO denetiminin sorduğu sorular serinin yazılarıyla örtüşüyor. Araçlar ise bu sorulara üç farklı yerden bakıyor: tek bir sayfanın render edilmiş hâline (Lighthouse), sitenin link'leriyle gezilen bütününe (Screaming Frog, Bing Site Scan, kendi crawler'ın) ve Google'ın index'teki kendi kaydına (URL Inspection).
| Kontrol | Serideki yazı | Tek sayfa (Lighthouse) | Site crawl'ı | Google'ın kaydı (URL Inspection) |
|---|---|---|---|---|
| Status code, redirect zinciri | 06, 07 | Yalnız sayfanın kendi status'u | Evet | Fetch durumu |
| Canonical tutarlılığı | 05 | Geçerlilik | Sitemap ile karşılaştırma | Google'ın seçtiği canonical |
| hreflang dönüş link'i | 10 | Yalnız biçim | Evet | Hayır |
| noindex, robots.txt | 02, 03 | Evet | Evet | Evet |
| Kırık link, orphan sayfa | 15, Content SEO | Hayır | Evet | Hayır |
| Title ve description tekrarı | SEO Temelleri | Yalnız var/yok | Evet | Hayır |
Tablonun özeti: tek bir araç yetmiyor. Lighthouse sayfanın kendisini, crawler sitenin yapısını, URL Inspection Google'ın sonucunu gösteriyor.
Lighthouse SEO Kategorisi: Tek Sayfa, Render Sonrası
Lighthouse 13.6.0'ın SEO kategorisinde on otomatik denetim var: is-crawlable, document-title, meta-description, http-status-code, link-text, crawlable-anchors, robots-txt, image-alt, hreflang ve canonical; bir de puana girmeyen, elle yapılacak structured-data denetimi. Paketin kendi configuration'ında is-crawlable'ın ağırlığı, bu denetim tek başına başarısız olduğunda bütün kategorinin de başarısız sayılacağı şekilde ayarlanmış. Kategorinin açıklaması da sınırını söylüyor: bunlar temel arama motoru optimizasyonu önerileri, sıralamayı etkileyen ama burada puanlanmayan birçok başka etken var. Lighthouse'un SEO denetimlerinin tek tek ne yaptığı developer.chrome.com'daki dokümanlarında; örneğin meta-description yalnız element'in var ve boş olmadığına bakıyor, açıklamanın kalitesini değerlendirmiyor.
Bookstore'un üç sayfasını ve bir önceki yazının taslağındaki katalog sayfasını (Playwright'ın Chromium'uyla) denetledim:
$ npx lighthouse "http://127.0.0.1:52213/books/genre/science-fiction?page=2" --only-categories=seo \
--output=json --output-path=page2-draft.json --chrome-path="$CHROMIUM" --chrome-flags="--headless=new" --quiet
$ jq -r '"\(input_filename): seo=\(.categories.seo.score*100), lighthouse \(.lighthouseVersion)"' dune.json video.json page2.json page2-draft.json
dune.json: seo=100, lighthouse 13.6.0
video.json: seo=100, lighthouse 13.6.0
page2.json: seo=100, lighthouse 13.6.0
page2-draft.json: seo=100, lighthouse 13.6.0
Taslaktaki sayfanın filtre link'leri şöyleydi:
$ curl -s "http://127.0.0.1:52213/books/genre/science-fiction?page=2" | grep -o '<a href="CatalogQuery[^>]*>' | head -2
<a href="CatalogQuery { Genre = science-fiction, Language = , Format = Hardcover, Sort = title, Page = 1, IsIndexable = False }.ToUrl(p)">
<a href="CatalogQuery { Genre = science-fiction, Language = , Format = Paperback, Sort = title, Page = 1, IsIndexable = False }.ToUrl(p)">
Razor href="@(Model.Query with { ... }).ToUrl(p)" ifadesinde @( )'yi bir ifade olarak çalıştırıp record'un ToString()'ini basmış, .ToUrl(p)'yi düz metin saymıştı; doğrusu @((Model.Query with { ... }).ToUrl(p)). Açılıştaki sorunun cevabı: crawlable-anchors link'in crawl edilebilir biçimde olup olmadığına bakıyor (href var mı, javascript: mı), link'in gittiği yerin 404 dönüp dönmediğine bakmıyor. Lighthouse tek bir sayfayı denetliyor; o sayfadan çıkan link'leri izlemiyor. "Lighthouse SEO 100" bu yüzden "sayfa temel kurallara uyuyor" demek; "site sağlıklı" ya da "sıralama iyi olacak" demek değil. JavaScript sonrası DOM (tarayıcının sayfadan kurduğu element ağacı) ile ilk HTML arasındaki farkı ise serinin sekizinci yazısında görmüştük.
Kendi Crawler'ın: Sitemap ve Link Graph
Lighthouse'un göremediğini görmek için siteyi bir crawler gibi gezmek gerekiyor. Bookstore'un denetim aracı tools/Bookstore.SeoAudit iki kaynaktan beslenen bir console uygulaması: link'lerle keşif, yani ana sayfalardan başlayan bir BFS (genişlik öncelikli arama), ve sitemap. Content SEO serisinin "Internal Linking" yazısında yazdığımız link graph'ın redirect zincirini izleyen ResolveAsync'i burada aynen kullanılıyor.
İlk tasarım kararı adreslerle ilgili. Test edilen uygulama http://127.0.0.1:52213'te çalışıyor, ama HTML'deki canonical, hreflang ve sitemap URL'leri production'daki gibi https://bookstore.example. Uygulamanın base URL'ini test ortamı için değiştirmek, test edilen HTML'in production'dakinden farklı olması demek. Bunun yerine crawler iki adresi eşliyor:
// Test edilen uygulamayı (ör. http://127.0.0.1:52213) production adresiyle (https://bookstore.example) eşler:
// HTML'deki canonical, hreflang ve sitemap URL'leri production'daki gibi kalır, istekler yerel uygulamaya gider.
public sealed class Fetcher(Uri target, Uri site)
{
private readonly HttpClient _http = new(new HttpClientHandler { AllowAutoRedirect = false }) { Timeout = TimeSpan.FromSeconds(10) };
// site'ın kendi URL'i mi? Öyleyse path + query, değilse null (dış link)
public string? ToPath(string url, string basePath = "/")
{
Uri resolved = new(new Uri(site, basePath), url);
bool own = resolved.Authority == site.Authority || resolved.Authority == target.Authority;
return own && resolved.Scheme.StartsWith("http") ? resolved.PathAndQuery : null;
}
// redirect zincirini izler: [ilk path, ..., son path] ve son status
public async Task<(List<string> Hops, int Status)> ResolveAsync(string path)
{
List<string> hops = [path];
for (int i = 0; i < 5; i++)
{
Page page = await GetAsync(hops[^1]);
if (page.Status is < 300 or > 399 || page.Location is null)
return (hops, page.Status);
hops.Add(ToPath(page.Location, hops[^1])!);
}
return (hops, 0); // 5 hop'tan uzun zincir ya da döngü
}
}
Crawler robots.txt'nin Disallow kurallarına uyuyor; Bookstore'un filtre URL'leri bu yüzden hiç istenmiyor. Her sayfada canonical, meta description ve <img>'lerin src ve alt'ı kontrol ediliyor, iç link'ler redirect zincirleriyle birlikte izleniyor. Ardından sitemap'teki her URL için dört soru soruluyor:
// 2) sitemap: her URL 200, index'lenebilir, canonical'ı kendisi ve link'le ulaşılabilir olmalı
foreach (string path in sitemap)
{
Page page = await fetcher.GetAsync(path);
if (page.Status != 200)
{
Add(Severity.Error, "sitemap-status", path, $"listed in sitemap but returns {page.Status}");
continue;
}
if (IsNoindex(page))
Add(Severity.Error, "sitemap-noindex", path, "listed in sitemap but has noindex");
string? canonical = Canonical(page);
if (canonical != fetcher.ToSiteUrl(path))
Add(Severity.Error, "sitemap-canonical", path, $"canonical is {canonical ?? "missing"}");
if (!_reachable.Contains(path))
Add(Severity.Warning, "orphan", path, "in sitemap but no internal link reaches it");
}
hreflang için kontrol, serinin onuncu yazısındaki karşılıklılık kuralı: her alternate 200 dönmeli ve bu sayfaya geri link vermeli.
// hreflang: her alternate 200 dönmeli ve bu sayfaya geri link vermeli
foreach (IElement link in page.Html!.QuerySelectorAll("link[rel=alternate][hreflang]"))
{
string href = link.GetAttribute("href")!;
if (href == self)
continue;
Page alternate = await fetcher.GetAsync(fetcher.ToPath(href)!);
if (alternate.Status != 200)
Add(Severity.Error, "hreflang-target", page.Path, $"{link.GetAttribute("hreflang")} -> {href} returns {alternate.Status}");
else if (alternate.Html?.QuerySelectorAll("link[rel=alternate][hreflang]").All(l => l.GetAttribute("href") != self) != false)
Add(Severity.Error, "hreflang-return", page.Path, $"{href} has no hreflang link back");
}
İlk Çalıştırma: Ne Buldu?
Crawler'ı ilk kez, bir önceki yazının taslak kodunu taşıyan uygulamaya karşı çalıştırdım:
$ dotnet run --project tools/Bookstore.SeoAudit -- http://127.0.0.1:52213/ https://bookstore.example/ > audit.txt
$ head -4 audit.txt | cut -c1-150
SEO audit http://127.0.0.1:52213/ (as https://bookstore.example/)
sitemap URLs: 96, pages reached by links: 104, requests: 136
ERROR broken-link /books/genre/classics /books/genre/CatalogQuery%20%7B%20Genre%20=%20classics,%20Language%20=%20,%20Format%20=%20
$ grep -c broken-link audit.txt; grep -c duplicate-title audit.txt; grep -c orphan audit.txt
40
41
8
$ grep -E "duplicate-title +/books/dune |orphan +/books/genre/science-fiction/tr" audit.txt
WARN duplicate-title /books/dune "Dune | Frank Herbert | Bookstore" also on /en/books/dune
WARN orphan /books/genre/science-fiction/tr in sitemap but no internal link reaches it
$ tail -1 audit.txt
40 errors, 49 warnings
Üç farklı bulgu, üç farklı ders.
40 kırık link, Lighthouse'un 100 verdiği Razor hatasıydı: on katalog sayfasında dörder filtre link'i. Hata bir önceki yazı yayımlanmadan düzeltildi ve oradaki kod düzeltilmiş hâliyle; buna rağmen yakalayan ilk şey test değil crawler oldu, çünkü testler yalnız önceden düşündüğümüz link'lere bakıyordu. Düzeltmeyle birlikte filtre link'lerini kontrol eden bir test de eklendi.
8 orphan, sitemap'te olan ama hiçbir sayfadan link verilmeyen dil sayfalarıydı (/books/genre/science-fiction/tr gibi). Bir önceki yazıda bu sayfaları index'lenecek filtreler olarak sitemap'e koymuş, tür sayfasından onlara link vermeyi unutmuştuk. Tür sayfasına "Türkçe" ve "İngilizce" link'leri eklendi; sitemap'teki kuralla aynı şekilde, yalnız listeyi daraltan diller için.
41 tekrar eden title ise aracın hatasıydı: bir kitabın Türkçe ve İngilizce sayfası aynı title'ı taşıyor (kitap adı ve yazar iki dilde aynı) ve ikisi hreflang ile bağlı dil sürümleri, rakip sayfalar değil. Kural dil başına gruplanacak şekilde değiştirildi. Her denetim aracının kuralları sitenin yapısına göre ayarlanmayı bekliyor; aynı şey Screaming Frog'un ya da Site Scan'in uyarıları için de geçerli.
Düzeltmelerden sonra:
$ dotnet run --project tools/Bookstore.SeoAudit -- http://127.0.0.1:52213/ https://bookstore.example/ seo-report.md
SEO audit http://127.0.0.1:52213/ (as https://bookstore.example/)
sitemap URLs: 96, pages reached by links: 114, requests: 114
0 errors, 0 warnings
Bulunacak bir şey olmayınca crawler'ın gerçekten bir şey bulabildiğini nasıl bilirsin? Aracın kendi testi bilinen hatalar içeren küçük bir site kuruyor: sitemap'te 301 dönen bir URL, noindex'li bir sayfa, canonical'ı başka sayfayı gösteren bir sayfa, geri link vermeyen bir hreflang hedefi, 404'e giden ve redirect'ten geçen birer link, alt'sız bir görsel ve hiçbir yerden link verilmeyen bir sayfa. Test, her birinin tam olarak bir kez bulunmasını bekliyor:
$ dotnet test tests/Bookstore.SeoAudit.Tests --logger "console;verbosity=normal" | grep Passed
Passed Bookstore.SeoAudit.Tests.AuditorTests.Finds_every_seeded_problem [95 ms]
Passed: 1
CI: Deploy Öncesi Kapı ve PR Yorumu
Crawler sıfırdan farklı exit code ile bitiyor ve istenirse Markdown bir rapor yazıyor; bu iki özellik onu bir CI adımına çeviriyor. Bookstore'un GitHub Actions workflow'u her pull request'te (PR) testleri çalıştırıyor, uygulamayı runner'da 127.0.0.1 üzerinde başlatıyor, crawler'ı çalıştırıyor ve raporu hem job özetine hem PR'a yorum olarak yazıyor:
- name: Start Bookstore.Web on 127.0.0.1
run: |
dotnet run --project src/Bookstore.Web --no-launch-profile --urls http://127.0.0.1:52213 &
for i in $(seq 1 60); do curl -sf -o /dev/null http://127.0.0.1:52213/ && exit 0; sleep 1; done
exit 1
env:
ASPNETCORE_ENVIRONMENT: Development
# HTML production'daki gibi (canonical https://bookstore.example), istekler yerel uygulamaya
- name: SEO crawl
id: crawl
continue-on-error: true
run: dotnet run --project tools/Bookstore.SeoAudit -- http://127.0.0.1:52213/ https://bookstore.example/ seo-report.md
- name: Job summary
if: always()
run: cat seo-report.md >> "$GITHUB_STEP_SUMMARY"
# fork'tan gelen PR'larda token yazma yetkisi yok; yorum yalnız aynı repo'nun branch'lerinde
- name: PR comment
if: always() && github.event.pull_request.head.repo.full_name == github.repository
run: gh pr comment "$PR" --body-file seo-report.md --edit-last --create-if-none
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
- name: Fail on errors
if: steps.crawl.outcome == 'failure'
run: exit 1
Workflow'u actionlint 1.7.12 ile kontrol ettim, hata vermedi. Birkaç ayrıntı: crawl adımı continue-on-error ile çalışıyor, böylece hata varken de rapor yazılıp PR'a düşüyor ve iş en sondaki adımda kırılıyor. gh pr comment'in --edit-last --create-if-none seçenekleri her push'ta yeni bir yorum açmak yerine botun son yorumunu güncelliyor. Yorum yazmak için job'a pull-requests: write izni veriliyor; fork'tan gelen PR'larda GITHUB_TOKEN bu yetkiyi almadığı için yorum adımı yalnız aynı repo'dan açılan PR'larda çalışıyor, job özeti ise her durumda yazılıyor.
Bu bölümde resmî belgelerden yararlanılmıştır.
Screaming Frog SEO Spider
Screaming Frog, teknik SEO denetiminin en yaygın masaüstü crawler'ı. Ücretsiz sürüm crawl başına 500 URL ile sınırlı, crawl'lar kaydedilemiyor ve configuration ile gelişmiş özellikler lisans gerektiriyor; ürünün sık sorulan sorular sayfasına ve kullanıcı rehberine göre JavaScript rendering de lisanslı özellikler arasında. Lisansla URL sınırı kalkıyor.
JavaScript rendering modunda araç sayfaları headless bir Chrome'da render ediyor ve hem ham hem render edilmiş HTML'i saklayabiliyor. JavaScript sekmesi, title, meta description, H1 (sayfanın ana başlığı), canonical ya da robots direktifi gibi alanların yalnız render sonrası ortaya çıktığı ya da JavaScript ile değiştiği sayfaları ayrı ayrı işaretliyor; ham ve render edilmiş HTML arasındaki kelime sayısı farkını da gösteriyor. Serinin sekizinci yazısındaki "ilk HTML'de ne var?" sorusunu bütün site için soran bir araç bu.
Kendi crawler'ınla ilişkisi rakiplik değil iş bölümü. Screaming Frog keşif için güçlü: bilinmeyen bir sitenin bütün sorunlarını ilk kez görmek, grafiklerle gezinmek, yüzlerce hazır kontrolle başlamak. Kendi crawler'ın ise regression için: sitene özgü birkaç kuralı her PR'da, ücretsiz ve tekrarlanabilir biçimde kontrol etmek.
Bu bölümde resmî belgelerden yararlanılmıştır.
Bing Webmaster Tools Site Scan
Site Scan, Bing Webmaster Tools'un içindeki, isteğe bağlı çalışan bir site denetim aracı; doğrulanmış bir site için Bing'in crawler'ı siteyi tarayıp yaygın teknik SEO sorunlarını raporluyor. Yardım sayfasına göre bir tarama üç kapsamdan biriyle başlıyor: siteyi Bingbot gibi link'lerden gezmek (bütün subdomain'ler dahil edilebiliyor), tek bir sitemap'teki URL'leri taramak ya da bir URL listesi. Taranacak en fazla sayfa sayısı, link derinliği sınırı, saniyedeki istek sayısı ve göz ardı edilecek URL parametreleri ayarlanabiliyor; robots.txt'yi yok saymak da bir seçenek. Siteye ayrılan aylık tarama kotasını Bing kendi sinyallerine göre hesaplıyor ve artırma talebi kabul edilmiyor.
Rapor sorunları üç önem derecesine ayırıyor: index'lenebilirliği etkileyebilecek hatalar, SEO sağlığını etkileyebilecek uyarılar ve en düşük öncelikli bildirimler. Her sorunun etkilediği sayfalar link derinliğine göre sıralanıp CSV (virgülle ayrılmış metin) dosyası olarak indirilebiliyor; en fazla beş taramanın sonucu saklanıyor. Tarama Bingbot user-agent'ıyla yapılıyor. Bookstore açısından anlamı: Google'ın göremediği şeyleri değil, Bing'in kendi crawler'ının siteyi nasıl gördüğünü gösteren, kurulum gerektirmeyen ikinci bir görüş.
Bu bölümde resmî belgelerden yararlanılmıştır.
Search Console URL Inspection API
Kendi crawler'ın ve Screaming Frog sitenin ne yayımladığını gösteriyor; Google'ın bununla ne yaptığını ise yalnız Google söyleyebilir. Search Console'daki URL Inspection aracının API'si https://searchconsole.googleapis.com/v1/urlInspection/index:inspect adresine bir POST: gövdede incelenecek URL (inspectionUrl), Search Console mülkü (siteUrl; URL-prefix mülklerinde sondaki / ile, domain mülklerinde sc-domain:bookstore.example biçiminde) ve isteğe bağlı mesaj dili (languageCode). Yetkilendirme OAuth ile, webmasters ya da webmasters.readonly kapsamıyla.
Yanıttaki indexStatusResult denetim için en değerli kısım: verdict (PASS, FAIL, NEUTRAL), coverageState, robotsTxtState, indexingState, pageFetchState, lastCrawlTime, googleCanonical ve userCanonical. Son ikisini karşılaştırmak, serinin beşinci yazısındaki "Google'ın seçtiği canonical senin verdiğinle aynı mı?" sorusunun toplu cevabı. Sınırlar da net: method dokümanına göre yalnız Google index'indeki sürümün durumu dönüyor, canlı URL testi yapılamıyor; kota mülk başına günde 2.000, dakikada 600 istek. Bookstore gibi binlerce kitabı olan bir sitede bu, bütün URL'leri değil, her gün değişen ya da sorunlu görünen bir örneklemi kontrol etmek demek: yeni yayımlanan kitaplar, crawler'ın uyardığı sayfalar ve her şablondan birkaç örnek.
Bu bölümde resmî belgelerden yararlanılmıştır.
Üretimde Dikkat
Crawler'ı production'a karşı da çalıştır, ama yavaş ve kendini tanıtarak. CI'daki tarama yerel uygulamaya karşı; production'daki reverse proxy, CDN (statik dosyaları kullanıcıya yakın sunuculardan dağıtan ağ) ve sunucu kuralları (serinin on ikinci yazısındaki taşıma doğrulaması gibi) farklı sonuç verebilir. Production taramasında istekleri sınırla ve user-agent'ta aracın adını ver.
Kuralları sitenin yapısına göre ayarla, ama gerekçesini yaz. Bookstore'da sayfalı listeler title tekrarından muaf, çünkü Google'ın pagination dokümanı buna izin veriyor; dil sürümleri muaf, çünkü hreflang ile bağlılar. Gerekçesi yazılmamış bir muafiyet zamanla gerçek sorunları da örter.
Uyarı ile hatayı ayır. Kırık link ve sitemap'teki 404 deploy'u durdurmalı; orphan sayfa ya da eksik description bir uyarı olarak PR'da görünmeli ama birleştirmeyi engellememeli.
Sık Düşülen Tuzaklar
Lighthouse SEO puanını hedef yapmak. 100, sayfanın temel kurallara uyduğunu gösterir; link'lerin çalıştığını, sitenin yapısının doğru olduğunu ya da sıralamayı değil.
Staging'i crawl edip sonucu production'a genellemek. Staging'in robots.txt'si, base URL'i ve verisi farklıdır.
Denetim aracının bütün uyarılarını sorgulamadan düzeltmeye çalışmak. Önce kuralın senin sitende anlamlı olup olmadığına bak.
Aracı yalnız bir kez, taşımadan ya da büyük bir değişiklikten önce çalıştırmak. Denetim, her deploy'da çalıştığında işe yarar.
URL Inspection API ile bütün siteyi taramaya çalışmak. Günlük kota buna yetmez; API, crawler'ın bulamayacağı tek şeyi, Google'ın kararını görmek için.
Küçük Bir Deneme
★☆☆ Kendi sitende Lighthouse'un SEO kategorisini üç farklı şablonda çalıştır, sonra aynı sayfalardan çıkan link'leri curl -s -o /dev/null -w "%{http_code}" ile tek tek iste. Lighthouse'un 100 verdiği bir sayfada kırık link var mı?
★★☆ Auditor'a bir redirect zinciri kuralı ekle: iç bir link iki veya daha fazla hop'tan geçiyorsa hata, tek hop'sa uyarı olsun. AuditorTests'teki sahte siteye /old2 -> /old -> /a zincirini ekleyip testi güncelle.
★★★ Crawler'a bir "Google'ın gözü" modu ekle: crawl sonunda uyarı veren sayfalardan en fazla 50'sini URL Inspection API ile sorgulayıp googleCanonical ile userCanonical'ın farklı olduğu sayfaları raporlasın. Kotayı aşmamak için istekleri dakikada 600'ün altında tutan bir rate limiter kullan ve API anahtarını yalnız CI secret'ı olarak ver.
Teknik SEO serisi burada bitiyor. URL tasarımıyla başladık; robots.txt, noindex, sitemap ve canonical ile crawl'ı ve index'i yönettik, status code, redirect ve JavaScript rendering ile sunucunun yanıtını doğru kurduk, Angular SSR (sayfanın sunucuda render edilmesi), hreflang, crawl budget ve site taşımayla ölçeği büyüttük; son blokta görselleri, videoları, katalog filtrelerini ve bütün bunları her deploy'da kontrol eden bir denetimi ekledik. Her adımda Google'ın kendi dokümanını ve Bookstore'un gerçek yanıtlarını esas aldık. Buradan sonraki doğal duraklar: sonuç sayfasındaki görünüm için Structured Data ve Schema.org serisi, sayfa hızı için Web Performansı serisi, içeriğin kendisi için Content SEO serisi, AI yanıt motorlarının siteyi nasıl okuduğu için GEO (AI tabanlı arama motorları için içerik optimizasyonu) serisi ve bütün bunların etkisini ölçmek için Analytics ve Ölçüm serisi.