Lighthouse, Rendering ve Coverage: Audit'leri Doğru Okumak
Lighthouse 13'te audit'ler Performance Insights ile birleşti ama skor yalnız metriklerden hesaplanıyor: cache-insight'ı düzeltince skor değişmedi, aynı sayfada üç koşu 89–91 arası oynadı. Simüle ve gözlenen LCP farkı, lab ve field verisi, navigation/timespan/snapshot modları, Rendering emülasyonları ve Coverage'daki yüzde 42'nin yalnız 2.6 KB'ının bizim kodumuz olduğunu source map ile göstermek.
Sürüm notu: Bu yazıdaki örnekler Chrome 156 (156.0.8078.80), Lighthouse 13.6.0 (CLI, Command Line Interface), Angular 22.2 ve .NET 10 ile denenmiştir. Lighthouse koşuları çalışma klasörüne kurulan CLI ile, her seferinde boş bir Chrome profiliyle yapıldı; Coverage ve Rendering çıktıları aynı Chrome sürümünde, headless modda Chrome DevTools Protocol (CDP) üzerinden alındı.
Önce Tahmin Et
Bookstore'un production build'ine Lighthouse'u üç kez arka arkaya çalıştırıyorsun. Üç Performance skoru da aynı mı çıkar? Rapor "Use efficient cache lifetimes" diye uyarıyor ve sen sunucuya doğru Cache-Control header'larını ekliyorsun; uyarı yeşile dönüyor. Performance skoru kaç puan artar?
İkinci soru: Coverage paneli ilk açılışta main-*.js'in yüzde 42'sinin kullanılmadığını söylüyor. Bu 58 KB'ın ne kadarı senin component'lerin, ne kadarı framework? Lazy route'a çevirince ne kadarını kazanırsın?
Lighthouse 13'te Ne Değişti?
Lighthouse 13 Ekim 2025'te çıktı ve en büyük değişiklik audit'lerin Performance panelindeki Insights ile birleştirilmesi oldu. Lighthouse 12 insight'ları varsayılan rapor görünümü yapmıştı; 13 eski audit'leri hem rapordan hem JSON (JavaScript Object Notation) çıktısından kaldırdı. Örneğin layout-shifts yerini cls-culprits'e, render-blocking-resources render-blocking'e, uses-long-cache-ttl cache'e, critical-request-chains ve uses-rel-preconnect network-dependency-tree'ye bıraktı. first-meaningful-paint, offscreen-images, uses-passive-event-listeners, no-document-write gibi bazı audit'ler ise karşılıksız kaldırıldı. Lighthouse ekibinin vurguladığı nokta: Performance skoru değişmedi. Skor audit'lerden değil metriklerden hesaplanıyor; değişen yalnız skora girmeyen audit'ler.
Bizim 13.6.0 raporumuzda bunu doğrudan görmek mümkün. Skoru belirleyen beş metrik ve ağırlıkları, insight audit'lerinin listesi:
scored [('first-contentful-paint', 10), ('largest-contentful-paint', 25), ('total-blocking-time', 30), ('cumulative-layout-shift', 25), ('speed-index', 10)]
insight audits 17 ['cache-insight', 'cls-culprits-insight', 'document-latency-insight', 'dom-size-insight', 'duplicated-javascript-insight', 'font-display-insight', 'forced-reflow-insight', 'image-delivery-insight', 'inp-breakdown-insight', 'lcp-breakdown-insight', 'lcp-discovery-insight', 'legacy-javascript-insight', 'modern-http-insight', 'network-dependency-tree-insight', 'render-blocking-insight', 'third-parties-insight', 'viewport-insight']
Insight'lar Performance panelinin Insights sekmesindekiyle aynı motoru kullanıyor; bir önceki yazıda gördüğümüz LCP (Largest Contentful Paint) breakdown burada lcp-breakdown-insight olarak geliyor. Lighthouse 13 Node 22.19 ya da üstünü istiyor. Raporumuzda bir de "Agentic Browsing" kategorisi vardı (llms-txt, webmcp-registered-tools gibi audit'lerle); bu kategori bu yazının kapsamı dışında.
Skoru Doğru Okumak: Lab Verisi ve Oynaklık
Aynı sayfaya üç koşu, her biri boş bir profille:
1 {'performance': 90, 'accessibility': 100, 'best-practices': 100, 'seo': 90, 'agentic-browsing': 100} {'first-contentful-paint': '0.8 s', 'largest-contentful-paint': '3.5 s', 'total-blocking-time': '120 ms', 'cumulative-layout-shift': '0', 'speed-index': '0.8 s'}
2 {'performance': 91, 'accessibility': 100, 'best-practices': 100, 'seo': 90, 'agentic-browsing': 100} {'first-contentful-paint': '0.8 s', 'largest-contentful-paint': '3.3 s', 'total-blocking-time': '110 ms', 'cumulative-layout-shift': '0', 'speed-index': '0.8 s'}
3 {'performance': 89, 'accessibility': 100, 'best-practices': 100, 'seo': 90, 'agentic-browsing': 100} {'first-contentful-paint': '0.8 s', 'largest-contentful-paint': '3.5 s', 'total-blocking-time': '110 ms', 'cumulative-layout-shift': '0', 'speed-index': '0.8 s'}
Hiçbir şey değişmeden skor 89 ile 91 arasında oynadı, LCP 3.3 ile 3.6 saniye arasında. Yerel bir sunucuda, sabit bir makinede bile durum bu; gerçek bir sitede ağ, reklam script'leri ve sunucu yükü oynaklığı büyütür. Tek bir koşuyla "skor düştü" demek yanlış; birkaç koşunun medyanına bak. Lighthouse'u DevTools'tan çalıştırıyorsan extension'lar da sonucu bozar; Incognito penceresi ya da ayrı bir profil kullan.
Bir de şu tuhaflık var: Lighthouse LCP'yi 3.5 saniye dedi, ama aynı raporun LCP breakdown insight'ı time to first byte'ı 2.3 ms, element render delay'i 123.5 ms ölçmüştü. Sebep varsayılan ayarlar:
throttling simulate mobile {'rttMs': 150, 'throughputKbps': 1638.4, 'requestLatencyMs': 562.5, 'downloadThroughputKbps': 1474.5600000000002, 'uploadThroughputKbps': 675, 'cpuSlowdownMultiplier': 4}
Lighthouse sayfayı throttling'siz yükler, gözlenen trace'ten mobil bir cihazda yavaş bir ağda ne olacağını simüle eder. Metrik değerleri bu simülasyondan, insight'taki alt parçalar ise gözlenen yüklemeden. İkisi aynı rapordadır ama aynı şeyi ölçmez.
Lab verisi kontrollü ortamda tek bir yüklemeyi ölçer; field verisi gerçek kullanıcıların 28 günlük dağılımıdır ve Google'ın CrUX'i (Chrome User Experience Report) üzerinden gelir. INP (Interaction to Next Paint) bir navigation raporunda ölçülmez, çünkü etkileşim yoktur; skordaki TBT (Total Blocking Time) onun lab'daki vekilidir. Lab "düzeldi" der, field "kullanıcı da hissetti" der; ikisi çeliştiğinde field haklıdır.
Insight'ı Düzeltmek, Skoru Değil
İlk raporda üç insight kırmızıydı:
FAIL/WARN cache-insight 0 Est savings of 270 KiB Use efficient cache lifetimes
FAIL/WARN network-dependency-tree-insight 0 Network dependency tree
FAIL/WARN render-blocking-insight 0 Est savings of 150 ms Render-blocking requests
{'url': 'http://localhost:5391/main-66HIL7BB.js', 'cacheLifetimeMs': 0, 'totalBytes': 139054, 'wastedBytes': 139054}
Kestrel static file'ları varsayılan olarak Cache-Control göndermiyor. Angular build'i dosya adlarına içerik hash'i koyduğu için bu dosyalar sonsuza kadar cache'lenebilir. Ama dikkat: ngsw-worker.js ve index.html hash'siz ve her deploy'da değişmeli; onları immutable yaparsan Application yazısındaki eski bundle sorunu kalıcı hâle gelir. Bu yüzden yalnız hash'li bundle'ları eşleştiren bir kural yazdık:
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// yalnız içerik hash'li bundle'lar immutable; ngsw-worker.js ve index.html her seferinde doğrulanmalı
bool hashed = HashedAsset().IsMatch(ctx.File.Name);
ctx.Context.Response.Headers.CacheControl = hashed ? "public, max-age=31536000, immutable" : "no-cache";
}
});
partial class Program
{
[System.Text.RegularExpressions.GeneratedRegex(@"^(main|chunk|styles|polyfills)-[A-Z0-9]{8}\.(js|css)$")]
private static partial System.Text.RegularExpressions.Regex HashedAsset();
}
main-66HIL7BB.js: Cache-Control: public, max-age=31536000, immutable
ngsw-worker.js: Cache-Control: no-cache
index.html: Cache-Control: no-cache
{'performance': 91, 'accessibility': 100, 'best-practices': 100, 'seo': 90, 'agentic-browsing': 100} 1 None 3.3 s
cache-insight artık geçiyor (skoru 1). Performance skoru 91: önceki üç koşunun aralığında. Önce Tahmin Et'teki sorunun cevabı bu; insight'lar skora girmiyor, gerçek kullanıcının tekrar ziyaretini hızlandırıyor. SEO'daki (Search Engine Optimization) 90'ın sebebi ise tek bir audit: meta-description, yani sayfada meta description yok. SEO audit'lerinin içeriği ilerideki Teknik SEO serisinin konusu.
Navigation, Timespan ve Snapshot Modları
Lighthouse panelindeki Mode seçeneği üç tür rapor üretir. Navigation, şimdiye kadar yaptığımız: sayfayı yükler ve yüklemeyi ölçer. Timespan, kaydı başlatıp senin sayfada yaptığın etkileşimleri (filtreye yazmak, sepete eklemek) ölçer; INP ve CLS (Cumulative Layout Shift) bu modda anlam kazanır, çünkü yüklemeden sonra olan şeyleri yakalar. Snapshot ise sayfanın o anki hâlini denetler: bir modal açıkken erişilebilirlik ve best practice kontrollerini çalıştırmak için. Bookstore için doğru akış şu: navigation ile yüklemeyi, timespan ile filtre etkileşimini, snapshot ile sepet sayfasının state'ini ölç. Aynı akışı Node'da Lighthouse'un user flow API'siyle (startFlow) script'leyip CI'da (Continuous Integration) çalıştırabilirsin; Lighthouse CI'ın kurulumu CI/CD serisinin işi.
Rendering Sekmesi: Görmediğini Görmek
Rendering sekmesini Command Menu'den "Show Rendering" ile ya da Drawer'dan açarsın. Paint flashing, yeniden boyanan alanları yeşil yanıp sönen kutularla gösterir: filtreye yazarken listenin tamamı yanıp sönüyorsa yalnız değişen kartlar değil her şey boyanıyordur. Layout Shift Regions, kayan alanları renkli bir katmanla işaretler; CLS'nin hangi element'ten geldiğini görmenin en hızlı yolu. Frame Rendering Stats, FPS (frames per second) ve GPU (Graphics Processing Unit) bellek göstergesini sayfanın üstüne koyar.
Aynı sekme medya özelliklerini de emüle eder. prefers-color-scheme ve prefers-reduced-motion'ı CDP'nin aynı komutuyla açtığımızda sayfa bunu böyle gördü:
--- emulated media: {"dark":true,"reducedMotion":true}
Angular uygulamanın karanlık temasını ya da animasyonları kapatan @media (prefers-reduced-motion: reduce) kurallarını işletim sisteminin ayarına dokunmadan böyle test edersin. "Emulate vision deficiencies" ise sayfayı protanopia, deuteranopia, tritanopia, achromatopsia ve bulanık görme ile gösterir; stokta yok bilgisini yalnız kırmızı renkle veren bir rozet, deuteranopia emülasyonunda yeşilden ayırt edilemez hâle gelir. Erişilebilirlik denetiminin içeriği ilerideki Accessibility (a11y) serisinde.
Coverage: Kullanılmayan JavaScript ve CSS
Coverage panelini Command Menu'de "Show Coverage" ile açar, kayıt düğmesine basıp sayfayı yenilersin. Her dosya için toplam ve kullanılmayan byte'ları, editörde de satır satır kırmızı/yeşil işaretleri gösterir. Production build'de ilk yüklemenin sonucu:
--- JS coverage after first load
main-MOBEH5XT.js total=139253 unused=58925 (42.3%)
chunk-WVN3REIB.js total=136552 unused=53330 (39.1%)
--- CSS rules tracked=7 used=7 unused=0
Rakam büyük görünüyor. Ama hangi kaynak dosyadan geldiğini bilmeden karar verme. Kullanılmayan aralıkları build'in hidden source map'iyle orijinal dosyalara çevirdik (20 karakterlik dilimlerle yaklaşık bir hesap):
main bundle, unused bytes by app source (approx.):
src/app/book-detail/book-detail.ts unused=871 used=209
src/app/book-list-slow/book-list-slow.ts unused=561 used=279
src/app/recently-viewed.service.ts unused=509 used=91
src/app/cart/cart.ts unused=282 used=178
src/app/book-list/book-list.ts unused=156 used=844
src/app/cart.store.ts unused=116 used=324
src/app/app.ts unused=110 used=990
src/app/book.service.ts unused=51 used=189
Önce Tahmin Et'teki ikinci sorunun cevabı burada: 58.9 KB'lık kullanılmayan kodun yalnız 2.6 KB kadarı bizim dosyalarımız. Listenin başındaki book-detail.ts, book-list-slow.ts ve cart.ts ilk sayfada hiç gösterilmeyen route'ların component'leri; app.routes.ts'te eager import edildikleri için main'e girmişler. Onları loadComponent ile lazy route'a çevirmek doğru, ama kazancı birkaç KB. Geri kalan 56 KB framework kodu: router'ın, HttpClient'ın, Service Worker client'ının o an çalışmayan yolları. Coverage "bu kod bu oturumda çalışmadı" der, "bu kod gereksiz" demez.
@defer için aday, ilk görünümde gerekmeyen ama aynı route'ta duran ağır bir parçadır: detay sayfasındaki yorumlar paneli, bir grafik, bir harita. Admin raporu zaten lazy route; ona ilk gidildiğinde yüklenen chunk-JFHRZQSQ.js (31.9 KB) yükleme anında yüzde 94.5 kullanılmamış görünüyordu: 300 durumlu bir switch'in yalnız 12'si çalışıyor. Lazy yüklemek byte'ları ertelemekle kalır, kullanılmayan kodu yok etmez. Bir not: CDP'nin Profiler.takePreciseCoverage komutu her çağrıda sayaçları sıfırlıyor; panel ise kayıt boyunca biriktirir. İki sayıyı karşılaştırırken bunu hesaba kat.
Üretimde Dikkat
Lighthouse skoru bir hedef değil, bir sinyaldir. Takımın "90 altına düşmesin" kuralı varsa en azından üç koşunun medyanını kullan ve ölçümü her seferinde aynı ortamda yap; bizim yerel ölçümümüzde bile iki puanlık oynama vardı. Kullanıcı deneyimine dair karar için CrUX ya da kendi RUM (Real User Monitoring) verine bak.
Cache header'larını hash'e göre ayır: hash'li dosyalar immutable, index.html, ngsw.json ve ngsw-worker.js her seferinde doğrulanır. Bir CDN (Content Delivery Network) kullanıyorsan aynı kuralı orada da kur; aksi hâlde sunucudaki doğru header'ı CDN ezer.
Coverage'ı tek bir sayfa yüklemesiyle değil, kullanıcının gerçek akışıyla kaydet. İlk ekranda çalışmayan kod sepete eklerken çalışabilir; onu lazy yaparsan ilk tıklamada bir network gecikmesi eklemiş olursun.
Sık Düşülen Tuzaklar
- Tek Lighthouse koşusuyla karar vermek; skor koşudan koşuya oynar.
- Insight'ı düzeltince skorun artmasını beklemek; Lighthouse 13'te skor yalnız metriklerden gelir.
- Simüle edilmiş LCP ile gözlenen LCP'yi karıştırmak.
- Bütün statik dosyalara
immutablevermek vengsw-worker.js'i dondurmak. - Coverage'daki kırmızıyı "silinebilir kod" diye okumak.
- Rendering emülasyonlarını açık unutup "tasarım bozuldu" diye bug açmak.
Küçük Bir Deneme
★☆☆ Bookstore'un production build'ine DevTools'un Lighthouse panelinden Incognito pencerede üç kez Performance raporu al. Üç skoru ve LCP değerini not et, medyanı hesapla.
★★☆ index.html'e bir meta description ekle ve SEO skorunun 100'e çıktığını doğrula. Ardından Timespan modunda filtreye "Comp" yazarken kayıt al ve raporda INP breakdown'u bul.
★★★ BookDetail, Cart ve BookListSlow'u loadComponent ile lazy route'a çevir. Build çıktısındaki main boyutunu ve Coverage'daki kullanılmayan byte'ları önce/sonra karşılaştır; kazancın beklediğin kadar olup olmadığını yaz.
Coverage ve Performance paneli bize "Angular ne yapıyor" sorusunu dolaylı olarak cevapladı. Bir sonraki yazıda doğrudan cevabı veren araca geçiyoruz: Angular DevTools'un component tree'si, profiler'ı, injector tree'si ve router tree'si.