Gerçek Teşhis: 66 Skorunun Arkasındaki Gizli Darboğazlar
Çoğu web geliştiricisi için performans optimizasyonu genellikle 'resimleri küçült' veya 'kodu minify et' tavsiyelerinden ibarettir. Ancak modern bir React veya TanStack Start uygulamasını mobil cihaz simülasyonunda (Moto G4 / 4G yavaşlatılmış ağ) Google Lighthouse ile test ettiğinizde, skorun 66'ya çakıldığını ve LCP (Largest Contentful Paint) değerinin 7.7 saniyeye kadar fırladığını görebilirsiniz.
Chrome DevTools Performance panelinde derin bir izleme (trace) kaydı aldığımızda, sorunun sadece görsel boyutu olmadığını keşfettik. Üç ana yapısal darboğaz vardı: 1) İlk HTML'in render edilmesini 180 ms boyunca kilitleyen üçüncü parti bir script, 2) Ana sayfa paketine istemeden gömülen 137 KB'lık tüm blog makalesi gövdeleri, ve 3) Sayfa açılışında ana iş parçacığını (main-thread) işgal eden analitik SDK'ları.
# Lighthouse Mobil Lantern Simülasyonu (Önceki Durum):
# Performans Skoru: 66 / 100
# FCP (First Contentful Paint): 3.1 s
# LCP (Largest Contentful Paint): 7.7 s
# TBT (Total Blocking Time): 520 ms
# Render-blocking kaynaklar: 180 ms gecikmeGörünmez Render-Blocking: Cloudflare 'email-decode.min.js' Tuzağı
Sayfa kaynağını incelediğinizde geliştiricilerin fark etmediği sinsi bir tuzak vardır: Cloudflare'in 'Email Address Obfuscation' (Scrape Shield) koruması. Eğer SSR HTML çıktınızda düz bir 'mailto:ad [at] ornek.com' kalıbı bulunuyorsa, Cloudflare edge sunucusu bunu otomatik olarak yakalar, e-postayı şifreler ve HTML body'sinin sonuna senkron çalışan bir 'email-decode.min.js' script'i enjekte eder.
Bu script harici bir ağ isteği oluşturur ve tarayıcı parser'ını kilitleyerek mobil 4G'de 170 ila 180 milisaniyelik kritik yol blokajı yaratır. Çözüm, SSR sırasında ham 'mailto:' protokolü üretmekten kaçınmaktır. E-posta adresini istemci tarafında parçalı dizilerle saklayıp sadece kullanıcı 'Kopyala' veya 'Gmail ile Yaz' butonuna tıkladığında dinamik olarak tetikleyerek Cloudflare'in bu ağır script'i enjekte etmesini tamamen engelledik.
// ÖNCE (ZARARLI): Düz e-posta href'i Cloudflare email-decode.min.js enjekte eder
<a href={"mailto:kullanici" + " [at] " + "alanadi.com"}>İletişim</a>
// SONRA (SIFIR BLOKAJ): Dinamik tetikleme ve bot-proof montaj
const EMAIL = ["kullanici", "alanadi.com"].join(String.fromCharCode(64));
<a href="/contact" onClick={(e) => { e.preventDefault(); window.location.href = `mailto:${EMAIL}`; }}>
{EMAIL}
</a>Ağır İçerikleri Ana Paketten Çıkarma: Sanal Modül ile Kod Bölünmesi
Modern SPA ve SSR çerçevelerinde sık yapılan bir hata, blog yazılarının tüm içeriğini merkezi bir veri dosyasından içe aktarmaktır. TanStack Start rotalarında loader veya beforeLoad fonksiyonları statik olarak makale listesini import ettiğinde, bundler tüm makalelerin metinlerini (137 KB JSON) ana sayfa bundle'ına gömer.
Kullanıcı yalnızca ana sayfadaki 3 satırlık başlıkları görmek isterken 18 makalenin tüm paragraflarını indirmek zorunda kalıyordu. Bunu çözmek için Vite derleme hattında bir sanal modül eklentisi (cmsContentPlugin) geliştirdik. Ana sayfa yalnızca başlık, tarih ve özetten oluşan hafif bir 'postIndex' nesnesini yüklerken; tam makale metinleri yalnızca '/blog/$slug' rotası istendiğinde dinamik import ile arka planda çekildi.
// scripts/cms-content-plugin.ts (Özet Mantık)
export function cmsContentPlugin(): Plugin {
return {
name: "published-cms-content",
resolveId(id) {
if (id === "virtual:site-posts") return "\0virtual:site-posts";
if (id === "virtual:site-index") return "\0virtual:site-index";
},
load(id) {
if (id === "\0virtual:site-index") {
// Sadece hafif kart metadatasını dışa aktar (5 KB)
return `export const postIndex = ${JSON.stringify(lightweightMetadata)};`;
}
if (id === "\0virtual:site-posts") {
// Tam makale gövdeleri sadece talep edildiğinde ayrı chunk'a ayrılır (137 KB)
return `export const posts = ${JSON.stringify(fullArticles)};`;
}
}
};
}Analitik Yükünü Sıfırlama: Etkileşimde Yükleme ve Ters Proxy
Üçüncü parti analitik kütüphaneleri (Google Analytics, Clarity, PostHog), web sitelerinin Total Blocking Time (TBT) skorunu en çok tahrip eden unsurlardır. Sayfa yüklenir yüklenmez 200 KB'lık bir script indirmek, ilk boyama anında JavaScript iş parçacığını kilitler.
Bu sorunu 'etkileşim tabanlı yükleme' (interaction-deferred loading) deseniyle çözdük. PostHog SDK'sını sayfa açılışında değil; kullanıcının ilk dokunma, tıklama, kaydırma veya tuşa basma eyleminde ('pointerdown', 'scroll', 'touchstart') arka planda asenkron olarak başlattık. Böylece açılış anında 0 KB analitik yükü oluştu ve TBT 0 ms'ye geriledi.
Ayrıca analitiğin reklam engelleyiciler (uBlock Origin, Brave Shields) tarafından bloklanıp %15-30 veri kaybına yol açmasını engellemek için Vercel üzerinde '/ingest' ters proxy'si (rewrites) kurduk. Tüm telemetri istekleri doğrudan 'umutgungorr.com/ingest' üzerinden aktığı için tarayıcı tarafından birinci parti (first-party) olarak kabul edildi.
// Vercel Ters Proxy (vercel.json):
{
"rewrites": [
{
"source": "/ingest/static/:path*",
"destination": "https://eu-assets.i.posthog.com/static/:path*"
},
{
"source": "/ingest/:path*",
"destination": "https://eu.i.posthog.com/:path*"
}
]
}
// İlk Etkileşimde Asenkron Başlatma (src/lib/posthog.ts):
const INTERACTION_EVENTS = ["pointerdown", "touchstart", "scroll", "click"];
function initOnFirstInteraction() {
const onInteract = () => {
INTERACTION_EVENTS.forEach(e => window.removeEventListener(e, onInteract));
requestIdleCallback(() => void import("posthog-js").then(mod => mod.default.init(...)));
};
INTERACTION_EVENTS.forEach(e => window.addEventListener(e, onInteract, { once: true, passive: true }));
}Sonuçlar: 100/100 Masaüstü ve 98 Mobil ile Gerçek Kazanımlar
Tüm bu optimizasyonların ardından canlı üretim ortamında bağımsız Lighthouse denetimi çalıştırıldığında elde edilen sonuçlar:
Masaüstü Lighthouse skoru 100 / 100 seviyesine ulaştı (FCP: 0.5s, LCP: 0.6s, TBT: 0ms, CLS: 0). Mobil skoru ise 66 puandan ve 7.7 saniyelik kabul edilemez gecikmeden, 98 / 100 puana ve 2.1 saniyelik LCP değerine çekildi. Üstelik hiçbir görsel tasarım, animasyon veya tipografiden ödün verilmedi.
# Optimizasyon Öncesi vs Sonrası Karşılaştırma Tablosu:
# ------------------------------------------------------------
# Metrik | Önceki Değer | Optimizasyon Sonrası
# ------------------------------------------------------------
# Mobil Lighthouse | 66 / 100 | 98 / 100 (Yeşil)
# Masaüstü Lighthouse | 84 / 100 | 100 / 100 (Kusursuz)
# Mobil LCP | 7.7 s | 2.1 s (↓ %72 Hızlı)
# Mobil FCP | 3.1 s | 1.8 s (↓ %42 Hızlı)
# Total Blocking Time | 520 ms | 0 - 50 ms
# Render-Blocking JS | 180 ms | 0 ms (Kaldırıldı)
# Ad Blocker Veri Kaybı | %25 - %30 | %0 (Ters Proxy ile)Kaynak: Google Chrome Developers: Optimize Largest Contentful Paint
Kaynak: PostHog Docs: Reverse Proxy on Vercel
Kaynak: TanStack Router: Preloading and Router State Optimization
