Frontend Performans Disiplini: 50 Yazıdan Çıkan 10 Temel Kural
50 yazının ortak çıktısı: ölçmeden hareket etme, render maliyetini anlama, bundle denetimini sürdürme. Frontend performans disiplinin on temel kuralı.
Frontend performansı birbirine bağlı kararlar zinciridir. Bir sayfanın hızlı açılması için yalnızca görselleri küçültmek yetmez; script yükleme sırası, font stratejisi, JavaScript bundle boyutu ve tarayıcının render süreci hepsinin aynı anda dengede durması gerekir. Bu denge sağlanmadan yapılan her optimizasyon ya yetersiz kalır ya da başka bir sorunu tetikler.
Bundle bölme, tree shaking, CSS boyutu, font optimizasyonu, React bileşen maliyeti, lazy loading: her konu başlı başına bir disiplin. Ortak altyapı ise ölçüm, eşik ve sıra. Neyi ölçeceğiniz, nerede duracağınız ve hangisini önce yapacağınız bu üçüne iner.
Bu kurallar hızlı kazanç listesi değil, bir kez uygulayıp geçilen adımlar da değil. Ekip büyüdükçe ve codebase karmaşıklaştıkça daha da değer kazanan prensiplerdir.
Ölçmek her kararın öncesine gelir
Her optimizasyon öncesinde bir baseline alın. Bu, ilk byte süresi (TTFB - Time To First Byte), LCP değeri veya INP skoru olabilir. Baseline olmadan iyileşmeyi kanıtlamak mümkün değildir; daha kötüsü, fark yaratmayan işe zaman harcanmış olur.
Chrome DevTools Performance sekmesi ve Core Web Vitals alan verileri bu iş için yeterlidir. Önemli olan hangi aracı kullandığınız değil, ölçümü tekrarlanabilir şekilde yapmanızdır: aynı ağ koşullarında, aynı sayfa durumunda, aynı önbellek senaryosunda. Sonuçlar tutarlı olursa yaptığınız değişikliğin etkisini nedensellikle bağlayabilirsiniz; "hızlandı" değil, "LCP 200 milisaniye düştü" diyebilirsiniz.
Ölçmeden yapılan optimizasyon tahmindir. Tahmin bazen doğru çıkar, ancak nedenini bilmezsiniz. Nedenini bilmediğiniz başarı bir sonraki projede tekrar edilemez, birikimli bir bilgiye dönüşmez.
Production verisi ayrıca önem taşır. Lab ortamında iyi görünen bir değişiklik, gerçek kullanıcıların düşük bant genişlikli bağlantılarında veya orta seviye cihazlarında farklı davranabilir. Core Web Vitals raporundaki 75. yüzdelik dilim alan verisini temsil eder; lab sonucu iyi olup alan verisi kötüyse sorun doğru simüle edilmemiş demektir.
Bundle boyutu aktif denetim ister
Bir kez küçülttüğünüz bundle zamanla büyür. Her yeni bağımlılık, her framework güncellemesi, her ekip üyesinin eklediği paket birikir. Bu birikim kademeli olduğu için fark edilmesi güçtür; altı ay sonra bakarsınız, ilk yüklenme süresi iki katına çıkmış.
webpack-bundle-analyzer veya Vite için eşdeğer görselleştirme araçlarını yaklaşık üç ayda bir çalıştırın. Şu üç kontrol noktasını alışkanlık haline getirin:
- En büyük beş paketi belirleyin ve her birinin gerçekten gerekli olup olmadığını sorun.
moment.jsyerinedate-fns,lodashyerine bireysel import gibi hafif alternatifleri değerlendirin. - Senkron
importile yüklenen ancak yalnızca belirli kullanıcı eylemlerinde ihtiyaç duyulan modülleri dinamikimport()'a taşıyın; sayfa ilk yüklenirken bu kod indirilmez, yalnızca gerektiğinde talep edilir. - Editörde
import-costeklentisi kullanın; bağımlılık boyutunu yazarken görün, ekleme kararı daha dikkatli verilir.
Bundle denetimi periyodik bir alışkanlıktır. Bir kez yapılan iş değildir.
Tree shaking, denetimin ayrılmaz parçasıdır. ESM formatında yayınlanmayan paketler tree shaking'den yararlanamaz ve bütün paketi dahil edersiniz. Bir bağımlılık eklemeden önce package.json'daki module veya exports alanını kontrol edin. CJS-only bir paket bundle'a olduğundan ağır biner; bireysel import desteği olan kütüphanelerde yalnızca kullandığınız fonksiyonu alın.
Render maliyeti: Paint, Layout ve Composite
Tarayıcı bir DOM değişikliğini işlerken üç farklı maliyetle karşılaşabilir. Layout, genişlik veya yükseklik gibi geometriyi etkileyen bir değişiklikte tetiklenir ve en pahalı işlemdir. Paint, renk veya arka plan gibi görsel bir değişiklikte çalışır. Composite ise yalnızca transform veya opacity değişikliklerinde devreye girer; GPU tarafından işlenir, ana iş parçacığını (main thread) meşgul etmez.
CSS animasyonlarınızı ve JavaScript tabanlı görsel değişikliklerinizi transform ve opacity üzerinden yapın. width, height, top, left gibi geometri özelliklerini animasyon döngüsünde değiştirmeyin. will-change özelliğini yalnızca gerçekten animasyon uygulanan elemanlara ekleyin; her elemana eklemek bellek tüketimini artırır ve ters etki yaratır.
Forced synchronous layout, JavaScript ile DOM okuması ve yazması art arda yapıldığında ortaya çıkar. el.offsetHeight okuduktan hemen sonra aynı elemana stil yazarsanız tarayıcı önce layout hesaplamak zorunda kalır; bu işlem döngü içinde tekrarlanırsa sayfa titrer. Okuma ve yazma işlemlerini ayrı gruplara ayırın, mümkünse requestAnimationFrame ile senkronize edin.
Ana iş parçacığını serbest tutmak
INP (Interaction to Next Paint), kullanıcı etkileşiminden bir sonraki ekran çizimine kadar geçen süreyi ölçer. Uzun süren JavaScript görevleri bu değeri artırır; sayfa yavaş değil ama "donuk" hissettirir. Fark önemlidir, çünkü kullanıcı LCP'yi sayıyla değil etkileşim hissiyatıyla algılar.
50 milisaniyenin üzerindeki herhangi bir JavaScript görevi "uzun görev" olarak sınıflandırılır. DevTools Performance sekmesinde kırmızıyla işaretlenen bu bloklar sorunun nerede olduğunu gösterir. Hesaplamayı parçalara bölmek için scheduler.yield() kullanın; tarayıcıya kontrol geri döner, bekleyen etkileşimler işlenir. Gerçekten paralel işlem gerekiyorsa Web Worker devreye alın; ana iş parçacığı tamamen serbest kalır.
React gibi framework'lerde bileşen yeniden render maliyeti ana iş parçacığına biner. Gereksiz render'ları azaltmak için memo, useMemo ve useCallback kullanılabilir, ancak bu araçların kendisi de hafif bir bellek maliyeti taşır. Profiler ile render sıklığını ölçmeden bu optimizasyonları uygulamak, çoğu durumda ölçülebilir bir fark yaratmaz.
Font stratejisi ve kritik yükleme yolu
Font, çoğu zaman LCP değerini artıran göze görünmez nedendir. Sayfa yüklenirken önce HTML ayrıştırılır, ardından CSS okunur; font dosyası CSS içinde tanımlandığı için bu aşamaya kadar tarayıcı isteği açamaz. Bağlantının yavaş olduğu durumlarda bu gecikme LCP'yi doğrudan etkiler.
Şu dört adım sırasıyla uygulandığında fark belirginleşir: <link rel="preload" as="font"> ile font isteğini HTML'in başına taşıyın; font-display: swap ile yedek font davranışını bildirin; unicode-range ile yalnızca kullanılan karakter bloğunu tanımlayın; yedek fontu asıl fontla yakın metrik uyumuna getirin. Son adım olmadan font-display: swap layout shift üretir, CLS puanını bozar. size-adjust, ascent-override ve descent-override ile yedek fontun satır yüksekliği ve karakter genişliğini asıl fonta yaklaştırmak bu kayıklığı ortadan kaldırır.
Bu optimizasyonlar ne zaman gereksizdir? Tek bir ağırlık ve stil kullanan, sistem fontuna geri dönebilen, kullanıcısının büyük çoğunluğunun hızlı bağlantıyla eriştiği bir projede font optimizasyonundan sağlanacak kazanım sınırlıdır. Ölçüm bunu gösteriyorsa, zamanı başka bir alana yönlendirin.
Lazy loading ve kod bölme kararı
Her bileşeni lazy load etmek doğru değildir. Kod bölme bir network isteği açar; yeterince büyük olmayan bir bileşen için bu istek, bileşeni baştan yüklemekten daha pahalı olabilir. Kural basittir: başlangıçta görünmeyecek, kullanıcıların önemli bir kısmının hiç açmayacağı, yaklaşık 30 kilobaytın üzerindeki parçalar için kod bölme anlamlıdır.
Modal, wizard adımları, ayarlar sayfası, dashboard grafikleri bu kategoriye girer. Navigasyon menüsü, sayfa başlığı, temel form bileşenleri girmez. React.lazy ile dinamik import ettiğiniz bileşenin Suspense sınırını doğru yere koyun; çok üst seviyeye konan bir sınır, tek bir bileşen yüklenirken büyük bir sayfa alanını gizler, kullanıcıyı gereksiz yere bekletir.
Görüntü lazy loading için HTML'nin loading="lazy" niteliği çoğu durumda yeterlidir. Ancak LCP görseline bu niteliği eklemeyin; tarayıcı o görseli erteleyeceği için LCP süresi artar. İlk ekranda görünen görsellere fetchpriority="high" ekleyin, daha hızlı indirilmesi için sinyal verin.
Cache, HTTP istek sayısı ve bağımlılık maliyeti
Cache süresi, bir dosyanın ne sıklıkla değiştiğiyle orantılı olmalıdır. JavaScript ve CSS bundle'larını içerik hash'iyle adlandırın: main.a4f2c3.js gibi. Böylece dosya değiştiğinde adı da değişir, tarayıcı önbelleği geçersiz olur; değişmeyen dosyalar ise uzun süre önbellekten sunulur. Hash tabanlı adlandırma olmadan ya çok kısa cache süresi belirlemek zorunda kalırsınız ya da kullanıcılar eski sürümü görmeye devam eder.
HTTP/2 ile küçük dosyalar artık büyük bir dosyaya kıyasla belirgin bir dezavantaj taşımaz. Bununla birlikte istek sayısını gereksiz yere artırmak anlamsızdır; beş satırlık satır içi CSS birleştirme optimizasyonu, küçük bir ek isteğin maliyetinden daha değerli olabilir. Bağlam önemlidir; kararı ölçüm verisiyle verin.
Bağımlılık seçiminde şu soruyu sorun: kullandığım kısım, bu paketin toplam boyutunun ne kadarı? Yalnızca tek bir işlev için 40 kilobaytlık bir kütüphane eklemek yerine o işlevi kendiniz yazın veya daha hafif bir alternatif bulun. Kütüphane ne kadar geniş bir API yüzeyi sunuyorsa, kullanmadığınız kodun bundle'a girme riski o kadar yüksektir.
Frontend performansı kararları teknik değil tasarım kararıdır. Neyi indir, ne zaman indir, ne ertelensin; bunların hepsinin bir maliyeti ve bir faydası var. Maliyet-fayda dengesini kurmak için veriye ihtiyaç duyarsınız; veri olmadan sezgiyle hareket edersiniz ve sezi her seferinde aynı sonucu vermez.
Kurallar bir kontrol listesidir, öncelik sıralaması değil. Her proje farklı bir darboğaza sahiptir; birinde font gecikme, birinde devasa bundle, birinde gereksiz yeniden render. Ölçüm hangi kuralın önce uygulanması gerektiğini gösterir; ölçüm olmadan bu liste rastgele taramaya dönüşür.
Bir optimizasyon tamamlandığında bitmiş sayılmaz. Kod değişir, bağımlılıklar güncellenir, yeni özellikler eklenir. Bu kuralların değeri tek seferlik uygulamada değil, ekip kültürünün bir parçası haline gelmesindedir. Bundle denetimi rutine girdiğinde, render maliyeti kod incelemelerinde konuşulduğunda, ölçüm alışkanlık olduğunda, performans borca dönüşmez.