Critical Rendering Path: HTML, CSS ve JavaScript Ekrana Nasıl Ulaşır, Neyi Render-Blocking Yapar?
Critical rendering path, ilk paint'e kadar tarayıcının beklediği en uzun zincirdir; head'deki stylesheet'ler render'ı, async/defer'siz script'ler parser'ı bloklar ama her blocking kaynak kritik yolda değildir. Bookstore ana sayfasının waterfall'unda font CSS'ini ve analytics'i düzeltmenin FCP'yi değiştirmediğini, asıl zincirin main.js olduğunu ölçüyor; response compression ve analytics'i load'dan sonra yükleyerek FCP'yi 2,35 sn'den 1,10 sn'ye indiriyoruz.
Sürüm notu: Bu yazıdaki örnekler Angular 22.2 (Angular 22.2'nin desteklediği TypeScript 6.0 ile), .NET 10 (ASP.NET Core 10), Lighthouse 13.6 ve Playwright 1.65 ile denenmiştir. 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).
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) Angular istemcisi bookstore-web'in kaynaktaki index.html'i şu. İlk yazıda kurduğumuz ölçümde ana sayfanın telefondaki FCP'si (First Contentful Paint; ekrana ilk metnin ya da görselin çizildiği an) 2,35 sn çıkmıştı:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bookstore</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="http://fonts.thirdparty.test:5302/css2?family=Inter:wght@400;700">
<script src="http://analytics.thirdparty.test:5302/analytics.js"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>
Okumaya devam etmeden önce tahmin et: <head>'deki iki üçüncü parti satır (bir font sağlayıcısının CSS'i ve bir analytics script'i) düzeltilirse FCP ne kadar düşer? Yarım saniye mi, bir saniye mi, hiç mi?
Ölçüm düzeni ilk yazıdakiyle aynı: ASP.NET Core API'si Angular build'ini sunuyor; Playwright ile başlatılan Chromium, "mobile" profilinde (4x CPU yavaşlatma, 150 ms gecikme, 1,6 Mbps) sayfayı beş kez açıyor ve değerleri uygulamanın kendi RUM (Real User Monitoring; gerçek kullanıcı ölçümü) kodundan okuyor. Font sağlayıcısı ve analytics sağlayıcısı gerçek değil: *.thirdparty.test adlarını Chromium'un --host-resolver-rules seçeneğiyle yerel bir Node sunucusuna yönlendiriyoruz ve o sunucu, uzak bir sunucunun yanıt süresini taklit etmek için her yanıta 250 ms ekliyor. Gerçek bir üçüncü partiye istek atmadık.
HTML'den Piksele: DOM, CSSOM, Render Tree

Tarayıcı HTML'i geldikçe parse eder ve DOM'u (Document Object Model; sayfanın tarayıcıdaki ağaç yapısı) kurar. Karşılaştığı her stylesheet'i indirip CSSOM'a (CSS Object Model; stil kurallarının ağaç hali) çevirir. İkisi birleşince render tree çıkar: ekranda gerçekten görünecek node'lar ve hesaplanmış stilleri. Ardından layout her kutunun yerini ve boyutunu hesaplar, paint çizim komutlarını üretir, composite de katmanları birleştirip ekrana verir. Critical rendering path, ilk paint'e kadar bu hattın üzerindeki bütün işlerin ve kaynakların zinciri.
Hattın iki durma noktası var. Birincisi CSS: tarayıcı stilsiz içeriği bir an gösterip sonra yeniden çizmek istemediği için <head>'deki stylesheet'ler inene kadar hiçbir şey çizmez. Bu render-blocking. İkincisi JavaScript: async ya da defer olmayan bir <script> DOM'u değiştirebileceği için parser onu indirip çalıştırana kadar durur. Bu parser-blocking. Script CSSOM'u da okuyabildiği için önündeki stylesheet inmeden çalışmaz; yani sync bir script kendinden önceki CSS'in gecikmesini de devralır.
Parser beklerken boş durmayan bir yardımcı var: preload scanner. Ham HTML'i ileriye doğru tarayıp src ve href'leri bulur ve parser'a sıra gelmeden indirmeye başlar. Bu yüzden HTML'de yazan bir <img> ya da <script src> erken keşfedilir; JavaScript'in sonradan DOM'a eklediği ya da CSS'te background-image olarak duran bir kaynak ise ancak onu ekleyen kod çalışınca keşfedilir. Bir sonraki yazının LCP (Largest Contentful Paint; viewport'taki en büyük içeriğin çizildiği an) sorunu tam olarak bu.
defer, async ve type="module"
| Etiket | Parser'ı durdurur mu? | Ne zaman çalışır? | Sıra korunur mu? |
|---|---|---|---|
<script src> | Evet, inip çalışana kadar | Hemen | Evet |
<script async> | Hayır (yalnız çalışırken) | İner inmez | Hayır |
<script defer> | Hayır | Parse bitince, DOMContentLoaded'dan önce | Evet |
<script type="module"> | Hayır (varsayılan olarak defer gibi) | Parse bitince | Evet |
Ayrıntılar JavaScript Temel serisindeki "JavaScript ile İlk Script: script Etiketi, defer, async ve DevTools" yazısında; burada önemli olan, bir script'in parser'ı durdurmaması ile kritik yolda olmaması arasındaki fark. Bu ayrımı birazdan ölçerek göreceğiz.
Angular Build'in Ürettiği index.html
Production build'in ürettiği index.html kaynaktakinden farklı:
<!doctype html>
<html lang="en" data-beasties-container>
<head>
<meta charset="utf-8">
<title>Bookstore</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="http://fonts.thirdparty.test:5302/css2?family=Inter:wght@400;700">
<script src="http://analytics.thirdparty.test:5302/analytics.js"></script>
<style>*{box-sizing:border-box}body{margin:0;font-family:Inter,system-ui,sans-serif;color:#0f172a;background:#f8fafc;line-height:1.5}</style><link rel="stylesheet" href="styles-SR7LJ3IE.css" media="print" data-beasties-media="all"><noscript><link rel="stylesheet" href="styles-SR7LJ3IE.css"></noscript></head>
<body>
<app-root></app-root>
<script src="main-EWTR2XZC.js" type="module"></script><script>document.querySelectorAll('link[data-beasties-media]').forEach(function(l){l.media=l.getAttribute('data-beasties-media');l.removeAttribute('data-beasties-media')})</script></body>
</html>
Üç şey dikkat çekiyor. Birincisi, styles.css'in ilk görünüm için gereken kısmı (burada body kuralları) beasties ile <style> olarak inline edilmiş; asıl dosya media="print" ile isteniyor. Ekranda eşleşmeyen bir media render'ı bloklamaz; body'nin sonundaki küçük inline script de media'yı all'a çeviriyor. Angular'ın inlineCritical optimizasyonu production build'de varsayılan olarak açık. İkincisi, main.js type="module" ile geliyor, yani parser'ı bloklamıyor. Üçüncüsü, elle eklediğimiz iki satır <head>'in en üstünde duruyor: Angular onlara dokunmuyor. angular.json'daki budget'lar ise yalnız Angular'ın ürettiği dosyaları sayıyor; bu iki satırın getirdiği byte'lar hiçbir budget'a girmiyor.
Bookstore Ana Sayfasının Waterfall'u
Waterfall'u DevTools'un Network paneli de gösteriyor (Browser DevTools serisinin Network paneli yazısı); tekrarlanabilir olsun diye Resource Timing API'den okuyan küçük bir script kullanıyoruz. Chromium 107'den beri her kaynak kaydında render'ı bloklayıp bloklamadığını söyleyen renderBlockingStatus alanı var (Firefox'ta yalnız önizlemede, Safari'de yok):
await page.goto(positionals[0] ?? 'http://127.0.0.1:5300/', { waitUntil: 'networkidle', timeout: 120_000 });
const data = await page.evaluate(async () => {
// LCP kayıtları yalnız buffered bir PerformanceObserver ile okunabiliyor
const lcp = await new Promise((resolve) =>
new PerformanceObserver((list) => resolve(list.getEntries().at(-1))).observe({ type: 'largest-contentful-paint', buffered: true }));
const fcp = performance.getEntriesByName('first-contentful-paint')[0];
const nav = performance.getEntriesByType('navigation')[0];
const rows = [nav, ...performance.getEntriesByType('resource')].map((e) => ({
name: e.name.replace(location.origin, '').replace('http://', '').slice(0, 56),
start: Math.round(e.startTime),
end: Math.round(e.responseEnd),
blocking: e.renderBlockingStatus,
}));
return { rows, fcp: Math.round(fcp?.startTime), lcp: Math.round(lcp?.startTime) };
});
resource start end render-blocking
/ 0 164 non-blocking
fonts.thirdparty.test:5302/css2?family=Inter:wght@400;70 201 485 blocking
analytics.thirdparty.test:5302/analytics.js 202 1235 blocking
/main-EWTR2XZC.js 203 2308 non-blocking
analytics.thirdparty.test:5302/collect 1310 1573 non-blocking
/styles-SR7LJ3IE.css 1310 1475 non-blocking
/api/books/featured 2396 2555 non-blocking
/api/books/new 2396 2569 non-blocking
/covers/dune.jpg 2571 16265 non-blocking
/covers/emma.jpg 2591 16654 non-blocking
/covers/the-hobbit.jpg 2591 16227 non-blocking
/covers/pride-and-prejudice.jpg 2591 17571 non-blocking
/covers/middlemarch.jpg 2591 17653 non-blocking
/covers/frankenstein.jpg 2591 18396 non-blocking
/covers/jane-eyre.jpg 2591 24381 non-blocking
/covers/moby-dick.jpg 2591 24575 non-blocking
/covers/war-and-peace.jpg 2591 24687 non-blocking
FCP 2420 ms, LCP 16272 ms
Font CSS'i ve analytics script'i blocking olarak işaretli. Analytics 1,2 sn'de bitiyor; o ana kadar parser bekliyor. Font dosyalarının listede olmaması da ayrıca not edilmeli: Chromium onları indirdi (aynı yüklemeyi DevTools protokolüyle izlediğimizde iki woff2 isteği görünüyor) ama başka bir origin'deki CSS'in başlattığı bu istekler sayfanın Resource Timing listesine düşmedi. RUM'la üçüncü parti kaynakları izlerken bu kör noktayı hesaba kat. Lighthouse da aynı iki kaynağı buluyor ve 900 ms kazanç tahmin ediyor:
Render-blocking requests: Est savings of 900 ms
analytics.thirdparty.test:5302/analytics.js totalBytes=76286 wastedMs=1603
fonts.thirdparty.test:5302/css2?family=Inter:wght@400;700 totalBytes=1806 wastedMs=854
Önce İki Bariz Düzeltme
Analytics script'inin sayfa içeriğiyle bir işi yok; parser'ı bekletmesi için sebep yok. Tek kelime yetiyor:
--- index.html
+++ index.html
@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="http://fonts.thirdparty.test:5302/css2?family=Inter:wght@400;700">
- <script src="http://analytics.thirdparty.test:5302/analytics.js"></script>
+ <script async src="http://analytics.thirdparty.test:5302/analytics.js"></script>
</head>
<body>
<app-root></app-root>
async script'i preload scanner yine erkenden indiriyor ama parser beklemiyor. Font CSS'i için iki yol var. Birincisi üçüncü partide kalıp bağlantıyı önceden açmak ve fontu display=swap ile istemek:
--- index.html
+++ index.html
@@ -6,7 +6,8 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
- <link rel="stylesheet" href="http://fonts.thirdparty.test:5302/css2?family=Inter:wght@400;700">
+ <link rel="preconnect" href="http://fonts.thirdparty.test:5302" crossorigin>
+ <link rel="stylesheet" href="http://fonts.thirdparty.test:5302/css2?family=Inter:wght@400;700&display=swap">
<script async src="http://analytics.thirdparty.test:5302/analytics.js"></script>
</head>
<body>
İkincisi font'u kendi origin'imizden sunmak. Font dosyaları (Inter, açık bir font lisansı olan Open Font License ile dağıtılıyor) public/fonts/'a kopyalanıyor, @font-face kuralları styles.css'e giriyor ve index.html'den font satırı tamamen çıkıyor:
/* Kendi origin'imizden Inter (SIL Open Font License, public/fonts/OFL.txt): latin + latin-ext, 400 ve 700 */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/inter-latin-ext-400-normal.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/inter-latin-400-normal.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
Beasties bu @font-face kurallarını da inline CSS'e taşıyor; tarayıcı font'u ayrı bir CSS isteği ve ayrı bir bağlantı beklemeden istiyor. Üç değişikliğin ölçümü (beş koşunun ortancası, ms):
| Durum | FCP | LCP |
|---|---|---|
| A: başlangıç | 2.348 | 16.196 |
B: analytics async | 2.348 | 16.196 |
C: B + preconnect + display=swap | 2.352 | 16.208 |
| D: B + font kendi origin'imizden | 2.344 | 16.016 |
| E: D + response compression | 1.496 | 15.256 |
F: E + analytics load'dan sonra | 1.104 | 14.852 |
İlk üç satır sürpriz: render-blocking iki kaynağı kaldırdık, FCP yerinden kıpırdamadı. Lighthouse ise D için başka bir şey söylüyor:
Lighthouse 5 runs, mobile: http://127.0.0.1:5300/
run score FCP LCP TBT CLS SI
1 71 606 12227 222 0.000 865
2 67 2111 12001 209 0.034 4367
3 72 610 12001 210 0.037 2438
4 72 610 12001 205 0.034 2457
5 67 2112 12001 206 0.034 4354
med 71 610 12001 209 0.034 2457
Lighthouse'un modeli beş koşunun üçünde FCP'yi 0,6 sn hesapladı; throttling'i gerçekten uygulayan ölçüm ise 2,34 sn diyor. Lighthouse varsayılan olarak sayfayı hızlı yükleyip yavaş cihazdaki zamanlamayı bir modelle tahmin ediyor ve bu sayfada tahmin gerçeği yakalamıyor. İnsight'ın "900 ms kazanç" tahmini de aynı modelden geliyordu.
Asıl Kritik Yol: main.js
Waterfall'a tekrar bak: <app-root> boş geldiği için ekrana çizilecek ilk içerik Angular çalışınca oluşuyor. Yani FCP'nin önündeki en uzun zincir HTML → main.js (282 KB) → Angular'ın ilk render'ı. CSS ve analytics bu zincirin üstünde değildi; main.js zaten 2,3 sn'de iniyordu ve onların bitmesini beklemiyordu. Critical rendering path'i kısaltmak, en uzun zinciri kısaltmak demek.
Zincirin en büyük parçası main.js'in byte'ları ve API onu sıkıştırmadan gönderiyordu. ASP.NET Core'da response compression iki satır:
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddSingleton<BookCatalog>();
builder.Services.AddSingleton<RumStore>();
// Metin yanıtlarını sıkıştır (HTML, JS, CSS, JSON); Brotli/zstd ayrıntıları navigasyon yazısında
builder.Services.AddResponseCompression();
WebApplication app = builder.Build();
app.UseResponseCompression();
app.UseStaticFiles();
Varsayılan ayarla Brotli ve gzip açılıyor, istemcinin Accept-Encoding'ine göre seçiliyor ve yanıta Vary: Accept-Encoding ekleniyor. main.js 282 KB'tan 114 KB'a indi ve FCP 2,34 sn'den 1,50 sn'ye düştü (tablodaki E). Varsayılan Brotli seviyesi en hızlısı; build sırasında önceden sıkıştırılmış dosya sunmak, seviyeler ve zstd navigasyonları hızlandırma yazısının konusu.
Son adım bant genişliği: async analytics parser'ı bloklamıyordu ama 76 KB'ı main.js ile aynı anda iniyor ve hattı paylaşıyordu. Analytics'in ilk paint'e ihtiyacı yok; load event'inden sonra yükleniyor:
--- index.html
+++ index.html
@@ -6,8 +6,14 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
- <link rel="stylesheet" href="http://fonts.thirdparty.test:5302/css2?family=Inter:wght@400;700">
- <script async src="http://analytics.thirdparty.test:5302/analytics.js"></script>
+ <script>
+ // Analytics load event'inden sonra: artık main.js ile bant genişliği için yarışmıyor
+ addEventListener('load', () => {
+ const script = document.createElement('script');
+ script.src = 'http://analytics.thirdparty.test:5302/analytics.js';
+ document.head.append(script);
+ });
+ </script>
</head>
<body>
<app-root></app-root>
FCP 1,10 sn'ye indi. Son durumun waterfall'u:
resource start end render-blocking
/ 0 157 non-blocking
analytics.thirdparty.test:5302/analytics.js 168 1260 non-blocking
/main-EWTR2XZC.js 168 1410 non-blocking
/styles-EHOBXKHN.css 170 329 non-blocking
/fonts/inter-latin-400-normal.woff2 181 638 non-blocking
analytics.thirdparty.test:5302/collect 1326 1582 non-blocking
/fonts/inter-latin-700-normal.woff2 1472 1770 non-blocking
/api/books/featured 1477 1634 non-blocking
/api/books/new 1478 1642 non-blocking
/covers/dune.jpg 1641 15233 non-blocking
/covers/the-hobbit.jpg 1655 15210 non-blocking
/covers/emma.jpg 1655 15629 non-blocking
/covers/middlemarch.jpg 1655 16649 non-blocking
/covers/pride-and-prejudice.jpg 1655 16664 non-blocking
/covers/frankenstein.jpg 1655 17376 non-blocking
/covers/moby-dick.jpg 1655 23572 non-blocking
/covers/jane-eyre.jpg 1655 23370 non-blocking
/covers/war-and-peace.jpg 1655 23684 non-blocking
FCP 1492 ms, LCP 15252 ms
Bu waterfall E durumundan; F'de analytics satırları load'dan sonraya kayıyor. LCP hâlâ 15 sn civarında: kapak görselleri 1,6 sn'de istenip on saniyelerce iniyor. Onları bir sonraki iki yazıda ele alacağız.
Resource Hint'ler: Tarayıcıya Önceden Haber Vermek
Resource hint'ler tarayıcıya henüz keşfetmediği bir şeyi söyler. Hepsinin bir bedeli var: tarayıcının zaten bildiği bir şeyi tekrar söylemek boşa, gereksiz yere erken istemek ise kritik kaynağın bant genişliğini çalar; F adımında gördüğümüz gibi.
preconnect: bir origin'e DNS, TCP ve TLS (şifreli bağlantı kurulumu) bağlantısını önceden kurar. Kritik kaynağı kesin olarak istenecek bir-iki üçüncü parti origin için. web.dev'e göre tarayıcı 10 saniye içinde kullanılmayan bağlantıyı kapatıyor; font gibi anonymous modda istenen kaynaklar içincrossoriginda gerekiyor, yoksa yalnız DNS çözülüyor.dns-prefetch: yalnız DNS çözümlemesi; daha ucuz, daha az kesin bağlantılar için.preload: belli bir kaynağı (font, LCP görseli, geç keşfedilen bir CSS) şimdi indirir.asdoğru verilmezse kaynak doğru önceliği alamaz ve script'ler iki kez inebilir; font preload'undacrossoriginşart.modulepreload: bir ES (ECMAScript; JavaScript'in standardı) module'ünü indirip parse eder; lazy chunk'lar için.fetchpriority:highya dalowile tarayıcının öncelik tahminini düzeltir; keşfi hızlandırmaz, yalnız sırayı değiştirir.
Bir de 103 Early Hints var: sunucu asıl yanıtı hazırlarken önce Link: <...>; rel=preload başlıklı bir ara yanıt gönderir, tarayıcı HTML'i beklemeden kritik kaynakları istemeye başlar (browser-compat-data'ya göre Chrome 103, Firefox 120, Safari 17). TTFB'si (Time to First Byte; HTML'in ilk byte'ının gelme süresi) uzun sayfalarda işe yarar. ASP.NET Core 10'un public API'sinde 1xx ara yanıt göndermeye yarayan bir method yok (referans assembly'lerinde yalnız HttpStatusCode.EarlyHints sabiti var); bu yüzden 103 genelde önündeki CDN (içeriği kullanıcıya yakın sunuculardan dağıtan ağ) ya da reverse proxy tarafından üretiliyor. Bu konu Domain, DNS ve CDN serisinde.
Üretimde Dikkat
Üçüncü parti script'ler zamanla birikir. Pazarlama ekibi tag manager'a bir pixel, destek ekibi bir chat widget'ı ekler ve hiçbiri kod review'dan geçmez. RUM'daki FCP ve LCP'yi deploy'lar kadar tag manager değişikliklerine göre de işaretle; bir sıçrama kod değişikliği olmadan da gelebilir. Kendi origin'imize taşıdığımız font'un cache ömrünü de düşün: dosya adında hash olmadığı için uzun bir max-age verirsen font güncellendiğinde eski dosya kalır. Angular'ın kendi çıktılarında bu sorun yok, onların adında hash var; HTTP caching ayrıntıları Caching serisinde.
Response compression'ı CPU maliyetiyle birlikte düşün: dinamik yanıtlar her istekte yeniden sıkıştırılıyor. Statik dosyalar için build sırasında sıkıştırılmış kopyalar üretip onları sunmak hem daha iyi oran hem sıfır istek anı maliyeti demek. HTTPS'te EnableForHttps seçeneği varsayılan olarak kapalı; açarken kullanıcıya özel veriyi gizli bilgiyle aynı yanıtta sıkıştırmanın BREACH (sıkıştırılmış HTTPS yanıtlarının boyutundan gizli bilgi çıkaran saldırı) tipi riskini değerlendir.
Sık Düşülen Tuzaklar
- "Render-blocking kaynak yok" ile "kritik yol kısa" aynı şey sanmak. Bu sayfada iki blocking kaynağı kaldırmak FCP'yi hiç değiştirmedi; kritik yol
main.js'ti. - Lighthouse insight'larındaki tahmini kazancı ölçmeden kabul etmek. Değişikliği önce/sonra gerçek throttling ile ölç.
- Her şeyi
preloadetmek. Preload edilen her kaynak kritik kaynakla aynı hattı paylaşır; Chrome kullanılmayan preload'lar için load'dan yaklaşık 3 saniye sonra console'a uyarı yazıyor. - Kullanılmayan
preconnect'ler ve font içincrossorigin'siz preconnect. async'i "maliyetsiz" sanmak. Parser'ı bloklamayan script de bant genişliği ve main thread zamanı harcar.- Kritik bir script'i JavaScript ile DOM'a ekleyerek "async" yapmak. Preload scanner onu göremez;
<script async src>daha erken iner. Analytics gibi kritik olmayan script'lerde ise geç keşfedilmek tam istediğimiz şey.
Küçük Bir Deneme
★☆☆ third-party.mjs'i DELAY_MS=1000 ile yeniden başlat ve başlangıçtaki index.html ile son hâlini waterfall.mjs'ten geçir. Hangi sürümün FCP'si üçüncü partinin yavaşlamasından etkileniyor?
★★☆ Kendi origin'imizden sunulan font için index.html'e <link rel="preload" as="font" type="font/woff2" crossorigin> ekle ve measure.mjs ile ölç. Sonra crossorigin'i sil ve DevTools'un Network panelinde font'un kaç kez indiğine bak.
★★★ third-party.mjs'teki analytics script'ini 60 ms yerine 300 ms main thread'i meşgul edecek şekilde değiştir. F durumunda FCP değişmeyecek; peki ilk yazının RUM tablosunda hangi metrik ve hangi route bundan etkilenir? Tahmin et, sonra field-sim.mjs ile doğrula.
FCP 2,35 sn'den 1,10 sn'ye indi ama LCP hâlâ 15 sn: kapak görseli JavaScript çalışıp API'den veri gelene kadar keşfedilmiyor ve sekiz büyük görselle bant genişliği paylaşıyor. Bir sonraki yazıda LCP'yi dört alt parçasına (TTFB, resource load delay, load duration, render delay) ayırıp her birini ayrı ayrı düşüreceğiz: kapağı gerçek bir <img> yapacak, NgOptimizedImage priority ile önceliklendirecek ve ana sayfayı prerender edeceğiz.