Mert Özen Her Satırda Daha İleri
Frontend Web Performansı — #10

2020'den 2026'ya Core Web Vitals ve Angular Performans Araçlarında Değişenler

Mert Özen 15 Kas 2026 10 dk 8 görüntülenme
2020'den 2026'ya Core Web Vitals ve Angular Performans Araçlarında Değişenler

2020'den bu yana CLS session window'lara geçti, INP Mart 2024'te FID'in yerini aldı, LCP ve INP Firefox ve Safari'de de ölçülür oldu ve 2026'da soft navigation metrikleri CrUX'e deneysel olarak girdi; Angular'da hydration, @defer, incremental hydration, zoneless ve OnPush varsayılan hale geldi. Bookstore'un ana sayfası bu serinin sonunda Lighthouse'ta 65'ten 97'ye çıktı; eski bir Angular 12 projesinde ilk bakılacak beş kalemi de çıkarıyoruz.

Sürüm notu: Bu yazıdaki ölçümler Angular 22.2 (Angular 22.2'nin desteklediği TypeScript 6.0 ile), web-vitals 6.2.3, Lighthouse 13.6, .NET 10 (ASP.NET Core 10) ve Playwright 1.65 ile alınmıştır. Bütün sayılar tek bir makinede, yerel build üzerinde, headless Chromium 158 ile alınmış lab ölçümleridir. Tarayıcı destek bilgileri MDN'in (Mozilla'nın web dokümantasyonu) browser-compat-data 8.1.9 sürümünden (2026-11-12); Angular sürümleri angular.dev'deki yol haritasından ve @angular/* 22.2 paketlerinin typing'lerindeki @publicApi notlarından doğrulandı.

Önce Tahmin Et

Serinin ilk yazısında sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) ana sayfası Lighthouse'ta 63-69 puan alıyordu ve telefon profilinde LCP (Largest Contentful Paint; sayfadaki en büyük içeriğin çizildiği an) 16 saniyeydi. Dokuz yazı sonra aynı sayfayı aynı araçla yeniden ölçtük. Okumadan önce tahmin et: puan kaç oldu? Ve 2020'de Core Web Vitals duyurulduğunda bu serideki metriklerden hangisi henüz yoktu?

2020-2024: Core Web Vitals, CLS'in Yeni Tanımı ve INP

web.dev'in "Web Vitals" yazısı 4 Mayıs 2020'de yayımlandı ve kullanıcı deneyimini üç metrikle tarif etti: yükleme için LCP, etkileşim için FID (First Input Delay; ilk etkileşimin bekleme süresi) ve görsel kararlılık için CLS (Cumulative Layout Shift; beklenmedik kaymaların toplam puanı). Eşikler bugünküyle aynı mantıkta kuruldu: kullanıcıların %75'inin deneyimi (p75) "good" sınırının altında olmalı. Metriklerin arama sıralamasına etkisi ve Search Console tarafı Analytics ve Ölçüm serisinin konusu.

İlk değişiklik CLS'in tanımında oldu. CLS başta sayfanın bütün ömrü boyunca bütün kaymaların toplamıydı ve uzun süre açık kalan sayfalar, özellikle SPA'lar (Single Page Application; route değişiminde sayfayı yeniden yüklemeyen uygulama), haksız yere kötü puan alıyordu. web.dev'in 7 Nisan 2021 tarihli "Evolving the CLS metric" yazısıyla CLS, aralarında 1 saniyeden az olan ve toplam 5 saniyeyi geçmeyen kaymalardan oluşan en büyük session window'un toplamına dönüştü. CLS yazısında impact × distance hesabını ve session window'ları bu tanımla ölçtük.

İkinci ve daha büyük değişiklik etkileşim metriği. FID yalnız ilk etkileşimin input delay'ini ölçüyordu: handler'ın ne kadar sürdüğünü ve ekranın ne zaman güncellendiğini görmüyordu, sonraki etkileşimleri de hiç saymıyordu. INP (Interaction to Next Paint; bir etkileşimden sonraki ilk paint'e kadar geçen süre) deneysel bir metrik olarak başladı, 2023'te "pending" oldu ve Mart 2024'te FID'in yerine stabil Core Web Vital oldu. INP yazısında katalogdaki aramayı 416 ms'den 8 ms'ye indirirken kullandığımız üç faz (input delay, processing, presentation delay) FID'in hiç görmediği kısımlar.

Tarayıcılar: LCP ve INP Artık Her Motorda

Uzun süre Core Web Vitals pratikte "Chrome Vitals" idi: metrikleri yalnız Chromium ölçebiliyordu ve field verisi yalnız Chrome kullanıcılarından geliyordu. browser-compat-data 8.1.9'a göre bu tablo değişti:

  • LCP (LargestContentfulPaint): Chrome 77, Firefox 122, Safari 26.2.
  • INP'nin dayandığı interactionId: Chrome 96, Firefox 144, Safari 26.2.
  • CLS'in dayandığı LayoutShift ve INP teşhisinde kullandığımız LoAF (Long Animation Frames; uzun frame'leri ve içlerindeki script'leri raporlayan API): yalnız Chromium.

RUM'da (gerçek kullanıcı ölçümü) bunun pratik anlamı şu: Safari ve Firefox'tan artık LCP ve INP raporu geliyor, CLS gelmiyor ve INP'nin longestScript gibi LoAF'a dayanan attribution alanları boş kalıyor. Route bazında p75'i tarayıcı ailesine göre de ayırmak, "iPhone'da LCP kötü" gibi bir bulguyu artık mümkün kılıyor.

2026: Soft Navigation'lar

Bu serideki her ölçüm bir SPA'nın ilk yüklenmesine ait. Kullanıcının uygulama içinde yaptığı route geçişleri (soft navigation) stabil Core Web Vitals'ta yeni bir sayfa sayılmıyor; INP ve CLS bütün oturum boyunca ilk açılan URL'e yazılıyor. Chrome 151 ile Soft Navigations API'si geldi ve web.dev'in 13 Ekim 2026 tarihli yazısına göre CrUX'te (Chrome User Experience Report; Chrome kullanıcılarından toplanan field verisi) soft navigation'ları da içeren deneysel LCP, INP ve CLS metrikleri yayımlanmaya başladı; stabil metriklere geçiş hedefi Mayıs 2027. web-vitals 6, desteklenen tarayıcılarda soft navigation'ları raporlayabiliyor; ilk yazıdaki RUM kodunda bunu bir alıştırma olarak bırakmıştık. Navigasyon yazısında ölçtüğümüz 810 ms'lik uygulama içi geçiş, bugünkü stabil metriklerde hiçbir yerde görünmüyor; soft navigation metrikleri stabil olunca görünecek.

Ölçüm Araçları: web-vitals ve Lighthouse

web-vitals library'sinin CHANGELOG'una göre büyük sürümler metriklerin hikâyesini takip ediyor: 3.0 (Ağustos 2022) bütün fonksiyonlara bir config parametresi ekledi ve TTFB'yi (Time to First Byte; sunucudan ilk byte'ın gelme süresi) bfcache dönüşlerinde de raporlamaya başladı; 4.0 (Mayıs 2024) INP için faz kırılımını ve LoAF attribution'ını getirdi ve onFID()'i deprecated yaptı; 5.0 (Mayıs 2025) onFID()'i kaldırdı ve destek politikasını Baseline Widely available tarayıcılara çekti; 6.0 (Temmuz 2026) soft navigation desteğini ekledi. Bu serinin RUM kodu 6.2.3 ile şu import'u kullanıyor:

import { onCLS, onFCP, onINP, onLCP, onTTFB, type MetricWithAttribution } from 'web-vitals/attribution';
  onTTFB(add);
  onFCP(add);
  onLCP(add);
  onINP(add);
  onCLS(add);

Eski bir projede getFID ya da onFID görüyorsan, o kod en az iki büyük sürüm geride; yükseltirken INP'yi ekle ve RUM tarafında FID kayıtlarını ayrı arşivle.

Lighthouse'ta skor formülü de değişti. Lighthouse dokümanındaki tabloya göre Lighthouse 8'de TTI (Time to Interactive; sayfanın etkileşime hazır sayıldığı an) %10, CLS %15 ağırlıktaydı; Lighthouse 10 TTI'ı skordan çıkardı ve CLS'in ağırlığını %25'e çıkardı. Lighthouse 13 (Ekim 2025) skor hesabını değiştirmedi ama eski teşhis audit'lerini DevTools'un Performance panelindeki insight'larla değiştirdi. Bu serinin son hâlinde Lighthouse 13.6'nın JSON raporunda 17 insight audit'i var (lcp-breakdown-insight, cls-culprits-insight, render-blocking-insight, network-dependency-tree-insight gibi) ve performans kategorisindeki ağırlıklar FCP (ilk içeriğin çizildiği an) 10, Speed Index 10, LCP 25, TBT (main thread'in bloklandığı toplam süre) 30, CLS 25. Aynı sayfanın ilk yazıdaki ve bugünkü ölçümü:

Lighthouse 5 runs, mobile: http://127.0.0.1:5300/
run  score    FCP    LCP    TBT    CLS     SI
1       64   3016  11927    217  0.033   4173
2       63   3010  11926    207  0.033   5107
3       65   2149  11926    232  0.033   5127
4       68   2154  11926    222  0.033   3328
5       69   2153  11926    208  0.033   3328
med     65   2154  11926    217  0.033   4173
Lighthouse 3 runs, mobile: http://127.0.0.1:5300/
run  score    FCP    LCP    TBT    CLS     SI
1       98    606   2185    133  0.000    606
2       97    605   2260    136  0.000    605
3       97    605   2259    135  0.000    605
med     97    605   2259    135  0.000    605

Puan 65'ten 97'ye, Lighthouse'un simüle ettiği LCP 11,9 sn'den 2,26 sn'ye indi. Lighthouse'un simüle edilmiş throttling'i ile bizim CDP (Chrome DevTools Protocol; tarayıcıyı programla yöneten protokol) ile uyguladığımız throttling farklı sayılar veriyor; ilk yazıdaki uyarı geçerli: lab sonucu bir yön gösterir, karar field verisinden çıkar.

Angular: NgOptimizedImage'dan injectAsync'e

Aynı dönemde Angular'ın performans araçları da baştan değişti. Yol haritasına ve 22.2 paketlerinin typing'lerine göre özet:

SürümPerformansla ilgili değişiklikBu serideki yeri
15NgOptimizedImage stableGörsel ve font yazısı: priority, loader, srcset
16DOM'u (sayfanın tarayıcıdaki ağaç yapısı) yeniden kurmayan hydration, developer previewSSR (sunucuda render) yazısı
17Hydration stable (provideClientHydration, @publicApi 17.0); @defer developer previewCLS ve JavaScript maliyeti yazıları
18@defer stable; event replay (developer preview) ve zoneless (deneysel) geldiSSR yazısında event replay
19Event replay stable ve yeni projelerde varsayılan; incremental hydration ve route bazında render modu developer previewhydrate on viewport, RenderMode
20Incremental hydration ve server route'ları stable; signal, effect, linkedSignal stable; zoneless 20.2'de stableINP yazısı
21Yeni projeler zonelessINP yazısındaki FrameRequestCallback
22Incremental hydration varsayılan; OnPush varsayılan (eski davranış Eager adıyla); resource ve httpResource stable; injectAsync; resource'a idJavaScript maliyeti ve SSR yazıları

Tablonun okunuşu: 2020'de bir Angular uygulamasında yükleme performansı için elinde lazy module'ler, budget'lar ve Angular Universal ile DOM'u silip yeniden kuran bir SSR vardı. Bugün route bazında render modu, sunucunun HTML'ini koruyan ve bölüm bölüm çalışan hydration, template seviyesinde @defer, servis seviyesinde injectAsync ve change detection'ı varsayılan olarak daraltan zoneless + OnPush var. Bu serinin bulgularından biri de bu araçların birbirini etkilediği: hydrate on viewport ile geç hydrate olan bir blok, uygulama stable olunca kapanan HTTP transfer cache'i kaçırıyor; lazy route, doğrudan girilen CSR (tarayıcıda render edilen) bir sayfada bir round trip ekliyor ve Angular'ın SSR motoru bunu modulepreload ile kapatıyor.

Eski Bir Angular 12 Projesinde İlk Beş Kalem

Angular 12, Mayıs 2021'de çıkmış, desteği çoktan bitmiş bir sürüm; bu tür bir projeye performans için girerken sıra önemli. Angular'ın güncelleme rehberi büyük sürümleri birer birer geçmeyi öneriyor; on büyük sürümlük bir yükseltme tek başına bir proje. Bu yüzden önce yükseltme gerektirmeyen ve en çok kazandıran işler:

1. Önce ölç: RUM ve p75

web-vitals 6, Angular'ın sürümünden bağımsız; ilk yazıdaki gibi bir sendBeacon ile route bazında p75 toplamak Angular 12'de de mümkün. Hangi sayfanın, hangi metrikte, hangi cihazda kötü olduğunu bilmeden yapılan her iyileştirme tahmin.

2. Görseller ve CLS: width/height, AVIF, LCP görseli

Boyutsuz bir kapak bu seride tek başına 0,54 CLS üretti, 440 KB'lık JPEG (klasik kayıplı görsel formatı) yerine 5,5 KB'lık AVIF (daha yeni ve daha verimli görsel formatı) LCP'yi 13,7 sn'den 1,3 sn'ye indirdi. Bunlar HTML ve sunucu tarafı değişiklikleri; Angular 12'de de yapılabilir. NgOptimizedImage için Angular 15 gerekiyor.

3. Budget ve lazy route

angular.json'daki budgets alanı eski projelerde de var; sınırları bugünkü boyutun biraz üstüne çek ki büyüme build'i kırsın. Angular 12'de lazy loading loadChildren ile module bazında; standalone component'lerle loadComponent sonra geldi. Büyük bir library'nin (chart, editör, harita) yalnız bir sayfada kullanıldığı her yer ilk aday.

4. Sıkıştırma, cache ve render-blocking kaynaklar

Sunucu tarafındaki ayarlar Angular sürümüne bağlı değil: Brotli seviyesi, önceden sıkıştırılmış dosyalar, hash'li dosyalara immutable, render-blocking üçüncü parti CSS ve script'ler. Bu seride sıkıştırma seviyesini değiştirmek katalogdaki listeyi 1,2 sn erkene çekti.

5. Yükseltme yolunda SSR ve change detection

Angular Universal'ın hydration'sız SSR'ı, tarayıcıda sayfayı silip yeniden kurduğu için CLS ve titreme üretebiliyordu. Hydration (17), @defer (18), incremental hydration (20) ve zoneless (20.2) ancak yükseltmeyle geliyor; yükseltme planını bu sürümleri ara durak yapacak şekilde kur. INP sorunu varsa OnPush'a geçişi beklemeden pahalı template method'larını computed'a (Angular 16+) ya da en azından saf pipe'lara taşımak eski sürümde de işe yarıyor.

Üretimde Dikkat

Bu serinin ölçümlerinin özeti, hepsi aynı makinede ve telefon profilinde alınmış lab sayıları:

YazıDeğişiklikÖlçülen sonuç
02Response compression, analytics'i load'dan sonra yüklemekAna sayfa FCP 2,35 sn'den 1,10 sn'ye
03Ana sayfayı prerender etmekLoad delay 1,1 sn'den 27 ms'ye
04AVIF, srcset, boyutlu görselAna sayfa LCP 13,69 sn'den 1,34 sn'ye
05computed, scheduler.yield(), CDK (Angular'ın Component Dev Kit'i) virtual scrollKatalog INP 416 ms'den 8 ms'ye
06Slot, skeleton, margin collapse düzeltmesiKitap sayfası CLS (scroll ile) 0,1079'dan 0'a
07Lazy route, @defer, budgetBaşlangıç JS 383 KB'tan 359 KB'a
08SSR, Output Cache, hydrate on viewportKitap sayfası FCP 1,28 sn'den 0,3 sn'ye
09Router preloading, Brotli Optimal, önceden sıkıştırmaUygulama içi geçiş 810 ms'den 207 ms'ye

Tablodaki her satır bir lab ölçümü ve field'da aynı oranda karşılık bulmayabilir. Üretimde karar hep aynı sırayla verilmeli: RUM'da hangi route ve metrik kötü, lab'da aynı senaryoyu kur, tek bir değişikliği ölç, RUM'da sonucun geldiğini doğrula. Metrikler de değişmeye devam ediyor; soft navigation metrikleri stabil olduğunda SPA'ların CrUX'teki tablosu değişecek ve bugün görünmeyen route geçişleri p75'e girecek.

Sık Düşülen Tuzaklar

  • FID'e göre "iyi" bir sayfanın INP'de de iyi olduğunu varsaymak; FID handler süresini ve sonraki etkileşimleri hiç görmüyordu.
  • Eski bir CLS raporunu (2021 öncesi, oturum boyu toplam) bugünkü değerle karşılaştırmak.
  • RUM'da tarayıcı ailesini ayırmamak; Safari'den INP gelir ama attribution'ın bir kısmı boş kalır, CLS hiç gelmez.
  • Lighthouse sürümleri arasında puanı karşılaştırmak; ağırlıklar 8'den 10'a değişti.
  • Angular'ı yükseltirken yeni varsayılanları (OnPush, zoneless, incremental hydration) gözden kaçırmak; migration'ı atlanıp OnPush'a geçen eski bir component'in binding'leri güncellenmeyi bırakabilir.
  • Bir Angular 12 projesinde performans için önce yükseltmeye girişmek; ölçüm, görseller ve sunucu ayarları beklemeden kazandırıyor.

Küçük Bir Deneme

★☆☆ lighthouse.mjs'i --save ile çalıştır ve JSON'daki insight audit'lerinden lcp-breakdown-insight ve network-dependency-tree-insight'ı lh-insight.mjs ile oku. Bu yazıdaki LCP alt parçalarıyla karşılaştır.

★★☆ rum.ts'e reportSoftNavs seçeneğini ekle ve ana sayfadan kitap sayfasına geçişin LCP ve INP raporlarını Chromium 158'de topla. Soft navigation'lar hangi route'a yazılıyor?

★★★ Bookstore'un katalog sayfasının Angular 12 ile yazılmış bir eşini düşün: loadChildren'lı bir module, zone.js ve ChangeDetectionStrategy.Default. Bu yazıdaki beş kalemi o projeye uygulamak için bir yükseltme planı çıkar: hangi adım hangi Angular sürümünü gerektiriyor ve her adımın sonunda hangi metriği ölçeceksin?

Web Performansı serisi burada bitiyor. Bookstore'un ana sayfası Lighthouse'ta 65'ten 97'ye, telefon profilindeki LCP 16,2 sn'den 1,45 sn'ye indi; katalogdaki arama 8 ms'de cevap veriyor ve kitap sayfası her senaryoda kaymıyor. Bundan sonrası field verisinin işi: aynı ölçümleri gerçek kullanıcılarla sürdürmek ve metrikler değiştikçe RUM kodunu güncel tutmak.