TTFB düşük ama FCP yüksekse sorun sunucuda değil render zincirindedir. Render-blocking CSS, @import zincirleri, senkron script'ler ve hatalı preload kullanımı TTFB'den bağımsız olarak ilk boyamayı saniyeler geciktirir.
TTFB Düşük Ama FCP Yüksek: Render Zincirinde Sorun Nerede?
Sunucu yanıtı 200 milisaniyenin altında dönüyor ama ekranda ilk piksel dört, beş saniye sonra beliriyor. Bu tabloyu gören çoğu ekip önce sunucuya bakar, cache katmanını kontrol eder, veritabanı sorgularını profiller ve hepsinin temiz çıktığını görünce şaşırır. Sorun sunucuda değildir; tarayıcının yanıtı aldıktan sonra ekrana bir şey çizene kadar geçen sürede saklanır.
TTFB ve FCP arasındaki bu boşluk render zincirinin kendisidir: HTML'in ilk baytı geldikten sonra tarayıcının hangi kaynakları hangi sırayla indirmesi, ayrıştırması ve uygulaması gerektiğine dair bir dizi bağımlılık. İlk boyama sunucudan bağımsız gecikir. Zincirin herhangi bir halkası gereksiz yere uzarsa, sunucu ne kadar hızlı yanıt verirse versin ekrandaki ilk piksel saniyelerce bekler.
Bu tanılama render zincirini adım adım izole etmeyi, hangi kaynağın gerçekten bloklayıcı olduğunu ve hangisinin sadece öyle göründüğünü ayırt etmeyi konu alıyor.
Hızlı sunucu yanıtı ilk boyamayı otomatik garanti etmez
TTFB, tarayıcının HTML dokümanının ilk baytını ne zaman aldığını ölçer. FCP ise ekranda ilk görsel içeriğin ne zaman belirdiğini ölçer. İkisi arasında tarayıcının HTML'i ayrıştırması, head içindeki kaynakları keşfetmesi, bloklayıcı CSS ve font dosyalarını indirmesi ve nihayet render ağacını oluşturması gerekir. Bu adımların her biri zaman alır ve TTFB'nin bu süreye hiçbir katkısı yoktur.
Pratikte şu görülür: TTFB 150 milisaniye, FCP 3200 milisaniye. Aradaki 3 saniyenin tamamı sunucu sonrası tarayıcı çalışmasında geçer. Bu ayrımı net görmeden yapılan her optimizasyon çabası yanlış katmana yönelir; sunucuyu daha da hızlandırmak, zaten dakikalar seviyesinde olmayan bir süreyi milisaniyeler mertebesinde kısaltır ve kullanıcı deneyiminde hissedilir bir fark yaratmaz.
Chrome DevTools Network panelinde sayfayı yeniden yükleyip ilk isteği (HTML dokümanı) bulun. Bu isteğin yanıt süresi TTFB'ye karşılık gelir. Ardından mavi çizgiyle işaretlenen FCP zaman damgasına kadar hangi isteklerin sıralandığını inceleyin. Bu aralıkta yer alan her istek, potansiyel bir gecikme adayıdır.
Genellikle bu boşlukta üç tip kaynak görülür: render-blocking CSS dosyaları, senkron yüklenen font dosyaları ve head içine yerleştirilmiş, defer veya async almamış script'ler. Waterfall'da bu isteklerin birbirini nasıl beklediğini (bir CSS dosyasının bitmeden diğerinin başlamadığını, ya da bir script'in CSS'i bekleyip sonra çalıştığını) görmek, zincirin nerede kopması gerektiğini gösterir.
Render-blocking CSS: @import zincirleri ve stil birikimi
Tarayıcı, head içinde bulduğu her <link rel="stylesheet"> etiketini varsayılan olarak render-blocking kabul eder. Bu dosya tamamen indirilip ayrıştırılana kadar tarayıcı hiçbir şeyi ekrana çizmez, çünkü stilsiz bir sayfa göstermek görsel olarak kararsız bir deneyim yaratır. Tek bir CSS dosyası küçükse bu sorun değildir; ama üç, dört ayrı stylesheet art arda sıralanmışsa, her biri kendi bağlantı ve indirme süresini zincire ekler.
Render-blocking kaynakları çözme mantığı burada doğrudan işler: sayfanın ilk görünümü için gerçekten gereken stilleri ayırıp geri kalanını ertelemek. Ama bunu yapmadan önce hangi CSS dosyasının ne kadar süre tuttuğunu waterfall'dan doğrulamak gerekir; küçük bir dosyayı optimize etmek zamanınızı boşa harcar.
CSS içinde @import kullanmak, ikinci bir dosyanın ancak birincisi indirildikten sonra keşfedilmesine yol açar. Tarayıcı paralel indirme yapamaz; önce ana dosyayı çeker, içindeki import satırını okur, sonra ikinci isteği başlatır. İki, üç seviyeli bir import zinciri, her seviyede bir round-trip süresi ekler ve bu süre TTFB'den tamamen bağımsız birikir.
Bu kalıp özellikle üçüncü taraf CSS çerçevelerinde veya eski temalarda sık görülür. Waterfall'da bunu tespit etmenin yolu, CSS isteklerinin birbirinin ardından art arda değil, birbirini bekleyerek sıralandığını görmektir. Çözüm genellikle basittir: import zincirini kaldırıp tüm dosyaları HTML'de doğrudan <link> etiketiyle paralel indirilecek şekilde tanımlamak.
Zamanla eklenen her yeni özellik genellikle kendi CSS dosyasını da beraberinde getirir: bir modal penceresi, bir kampanya banner'ı, bir üçüncü taraf widget'ı. Bu dosyalar tek tek küçük görünür ama head'de birikince toplam render-blocking süresi fark edilmeden büyür. Kimse tek bir eklemeyi sorunlu bulmaz, ama altı ay sonra head'de sekiz stylesheet birden bulunabilir.
Critical CSS ayrımı tam olarak bu birikimi düzeltmek için vardır: sayfanın görünür alanı için gereken minimum stil setini belirleyip inline etmek, geri kalan her şeyi (modal, banner, widget stilleri dahil) asenkron yüklemeye almak. Bu ayrımı yaparken her yeni özelliğin CSS'ini varsayılan olarak kritik kabul etmemek, birikimi baştan önler.
Head içindeki script sırası ve üçüncü taraf sızması
Head bölümüne defer veya async niteliği olmadan yerleştirilen bir script, tarayıcının HTML ayrıştırmasını tamamen durdurur. Tarayıcı script'i indirir, çalıştırır ve ancak ondan sonra ayrıştırmaya devam eder. Script CSS dosyasından sonra geliyorsa, önce CSS'in bitmesini bekler; bu da iki bloklayıcı kaynağın art arda sıralanması anlamına gelir.
Analytics kodu, tag manager veya bir A/B test kütüphanesi genellikle bu şekilde head'e sızar. Kodun kendisi küçük olabilir ama bağlantı kurma, DNS çözümleme ve sertifika doğrulama gibi ağ maliyetleri, dosyanın boyutundan bağımsız olarak zincire zaman ekler. Bu script'lerin head'de mi yoksa body sonunda mı olduğunu kontrol etmek, ilk bakılacak yerlerden biridir.
Bir tag manager container'ı kurulum sırasında genellikle head'in en üstüne yerleştirilir; bu, dokümantasyonlardaki varsayılan öneridir. Ama container kendisi render-blocking olmasa da, içinden tetiklenen bazı etiketler senkron script yükleyebilir ve bu script'ler render ağacının oluşumunu geciktirebilir.
DevTools'ta Network panelini "Initiator" sütununa göre incelemek, hangi script'in hangi kaynağı tetiklediğini gösterir. Tag manager container'ından sonra art arda gelen üç dört senkron istek görüyorsanız, bunlardan kaçının gerçekten sayfa yüklenirken (etkileşimden önce) çalışması gerektiğini sorgulamak yerinde olur.
Font yüklemesi ve preload rekabeti
Font dosyaları doğrudan render-blocking değildir ama CSS içinde tanımlandıkları için CSS dosyasının indirilmesini beklerler, sonra kendileri indirilir. Eğer sayfanın ilk metni bu fonta bağlıysa ve font-display değeri varsayılan (block) bırakılmışsa, tarayıcı font gelene kadar metni hiç göstermeyebilir. Bu durumda FCP doğrudan font indirme süresine bağlı hale gelir.
Font yükleme stratejisini gözden geçirirken önce şunu netleştirin: sayfanın ilk boyaması bir font gerektiriyor mu, yoksa sistem fontuyla da başlayıp sonra mı geçiş yapabilir? İkinci seçenek neredeyse her zaman daha hızlı bir FCP üretir, çünkü tarayıcı font beklemeden metni çizebilir.
<link rel="preload"> tarayıcıya bir kaynağı erken indirmesini söyler, ama önceliği de değiştirir. Sayfada birden fazla preload varsa, bunlar aynı anda bant genişliği için yarışır ve gerçekten kritik olan kaynak (örneğin ilk render'ı bloklayan CSS) diğerleriyle aynı sıraya girip beklenenden geç tamamlanabilir.
Sık görülen hata, hem CSS dosyasını hem birkaç fontu hem de bir hero görselini aynı anda preload etmektir. Bunların hepsi "önemli" olabilir ama hepsi aynı anda önemli değildir. Preload listesini gerçek render sırasına göre önceliklendirmek, sadece bir veya iki gerçekten kritik kaynakla sınırlı tutmak, rekabeti azaltır.
DevTools ile zincir izleme ve kritik CSS ayıklama
Pratik bir izleme sırası şöyle kurulabilir: önce sayfayı sıfır cache ile yeniden yükleyin ve Network panelinde "Disable cache" seçeneğini açık tutun. FCP zaman damgasına kadar tamamlanan tüm istekleri filtreleyin. Her isteğin "Waterfall" sütunundaki bar'ına tıklayıp "Queueing", "Connection start" ve "Waiting" sürelerinin ne kadarını oluşturduğuna bakın.
Bir isteğin uzun "Queueing" süresi, tarayıcının aynı anda çok fazla bağlantı açmaya çalıştığını gösterir. Uzun "Waiting" süresi ise kaynağın kendisinin (sunucu tarafında) yavaş olduğunu işaret eder. Bu ayrım, sorunun ağ tarafında mı yoksa kaynağın büyüklüğünde mi olduğunu netleştirir.
Chrome DevTools'un Coverage paneli, yüklenen bir CSS dosyasının yüzde kaçının ilk render sırasında gerçekten kullanıldığını gösterir. Bir dosyanın yüzde seksen kullanılmayan kural içerdiğini görmek, o dosyanın render-blocking listesinden çıkarılıp asenkron yüklemeye alınması gerektiğine dair somut bir gerekçe verir.
Bu ölçümü sayfa tipine göre tekrarlamak önemlidir; anasayfa için kritik olan bir stil bloğu, bir ürün sayfasında hiç kullanılmayabilir. Tek bir global CSS dosyasıyla çalışan siteler, bu yüzden her sayfa tipinde farklı oranda gereksiz yük taşır.
Müdahale sırası ve değişikliği doğrulamak
Önce en büyük tekil gecikmeyi hedefleyin: waterfall'da en uzun süren tek bir bloklayıcı kaynak varsa (örneğin @import zinciri veya senkron bir üçüncü taraf script), onu çözmek genellikle en yüksek getiriyi sağlar. Ardından kritik olmayan CSS'i ayırıp asenkron hale getirin, font stratejisini gözden geçirin ve son olarak preload listesini sadeleştirin.
Her müdahaleyi tek tek uygulayıp ölçmek, birden fazla değişikliği aynı anda yapmaktan daha güvenilir sonuç verir. Beş değişikliği birlikte yaptığınızda FCP düşse bile hangisinin gerçek etkiyi yarattığını bilemezsiniz; bu da bir sonraki sayfada aynı stratejiyi tekrarlarken kör noktalara yol açar.
Her müdahaleden sonra aynı koşullarda (aynı ağ profili, aynı cihaz simülasyonu, disable cache açık) yeniden ölçüm yapın. FCP'nin yanı sıra LCP değerinin de nasıl etkilendiğine bakın; bazen FCP hızlanırken LCP değişmeyebilir, çünkü ana içerik farklı bir kaynağa bağlıdır. Bu ikisini birlikte izlemek, yapılan müdahalenin gerçek kapsamını gösterir.
Lab ölçümü olumlu çıksa bile değişikliği canlıya almadan önce sınırlı bir trafik dilimiyle test etmek, beklenmedik bir yan etkiyi (örneğin bir stil dosyasının geç yüklenmesi yüzünden kısa süreli düzen kayması) erken yakalamanızı sağlar.
TTFB düşükken FCP yüksek çıkan bir sayfada aranan şey tek bir büyük hata değil, genellikle birikmiş küçük gecikmelerin toplamıdır: bir @import zinciri, head'e sızmış bir script, gereksiz yere preload edilmiş bir kaynak. Bunların her biri tek başına önemsiz görünür, ama art arda dizildiğinde saniyeler mertebesinde bir gecikme üretir.
Bu tanılama sırası, semptomdan nedene giden genel yaklaşımın render zincirine özel bir uygulamasıdır. Sunucu tarafını temiz bulduğunuzda araştırmayı bırakmak yerine, tarayıcının HTML'i aldıktan sonra ne yaptığına bakmak, çoğu zaman gerçek darboğazın orada durduğunu gösterir.