Script Yönetimi / JavaScript performansı

JavaScript Performans Optimizasyonu: Defer, Async ve Bundling

JavaScript Performans Optimizasyonu: Defer, Async ve Bundling için performans görseli

Defer, async ve bundling tercihleri doğru sıralanmadığında sayfa açılır görünse bile etkileşim gecikmesi kullanıcıyı yorar.

Script yürütme sırası, kritik kod ayrımı ve bundle parçalama kararları gerçek kullanıcı akışına göre değerlendirildiğinde tıklamaya hızlı yanıt veren bir arayüz davranışı elde edilir.

Defer ve async arasındaki fark nedir?

HTML'de script etiketi varsayılan olarak "senkron" çalışır: tarayıcı scripti gördüğünde HTML ayrıştırmayı duraklatır, scripti indirir, çalıştırır ve ancak sonra devam eder. Bu, özellikle head bölümündeki büyük scriptler için ciddi yüklenme gecikmelerine yol açar.

async özelliği, script dosyasının HTML ayrıştırma ile paralel indirilmesini sağlar; indirme tamamlandığında HTML ayrıştırma yine duraklar ve script çalışır. Hangi script önce indirilirse önce çalışır; yürütme sırası garanti değildir. Bu nedenle birbirine bağlı (dependent) scriptlerde async kullanmak sorun yaratabilir. Analytics, reklam scriptleri ve birbirinden bağımsız widget'lar async için uygun adaylardır.

defer özelliği, scriptin HTML ayrıştırma ile paralel indirilmesini sağlar; ancak çalıştırma işlemi HTML ayrıştırma tamamlanana kadar ertelenir. Birden fazla defer edilmiş script, HTML'de görünme sırasıyla çalışır; bu sıra garantisi birbirine bağımlı scriptler için önemlidir. Genellikle async'ten daha güvenli tercih olarak öne çıkar.

Code splitting: büyük bundle'ı parçalara bölmek

Modern JavaScript uygulamalarında tüm kod tek bir büyük dosyada (bundle) toplanır. Kullanıcı yalnızca giriş sayfasını açsa bile o sayfada hiç kullanılmayan yüzlerce kilobayt kod yüklenir ve ayrıştırılır. Bu gereksiz yük ana iş parçacığını meşgul eder, TBT'yi artırır ve etkileşim gecikmesine yol açar.

Code splitting bu sorunu çözer: uygulama kodu, yalnızca ihtiyaç duyulduğunda yüklenen daha küçük parçalara bölünür. Webpack, Vite ve Rollup bu işlemi otomatik olarak destekler. Route bazlı bölme en yaygın yaklaşımdır; her sayfa yalnızca kendi kodunu yükler. Dinamik import (import()) sözdizimi, belirli bir bileşenin yalnızca gerektiğinde yüklenmesini sağlar.

Örneğin bir e-ticaret sitesinde ödeme formunun kodu, kullanıcı ödeme adımına gelene kadar indirilmez. Harita bileşeni yalnızca iletişim sayfasında yüklenir. Bu yaklaşım ilk yükleme süresini kısaltır; kullanıcı henüz kullanmadığı özellikleri beklemek zorunda kalmaz.

  • Route bazlı bölme: React Router, Next.js veya Vue Router ile her sayfa kendi JavaScript chunk'ını alır; anasayfa bundle'ı küçük kalır.
  • Dinamik import: import('./HeavyComponent') ile ağır bileşenler yalnızca ihtiyaç anında yüklenir; modal, tab paneli, carousel için ideal.
  • Vendor chunk ayrımı: React, Lodash gibi kütüphaneler ayrı chunk'ta tutulursa değişmediğinde yeniden indirilmez; tarayıcı cache'den sunar.
  • Bundle analizi: webpack-bundle-analyzer veya Vite'ın rollup-plugin-visualizer aracıyla hangi paketin ne kadar yer kapladığını görün; gereksiz büyük bağımlılıkları tespit edin.

Tree shaking ve kullanılmayan kodu ayıklamak

Tree shaking, derleme sürecinde hiç kullanılmayan kodun (dead code) nihai paketten çıkarılması işlemidir. Bir kütüphaneden yalnızca tek bir fonksiyon kullanılıyorsa kütüphanenin tamamını bundle'a eklemek yerine yalnızca o fonksiyon dahil edilir. Webpack ve Rollup modern JavaScript modül sözdizimini (ES modules) kullandığında bu işlemi otomatik yapar.

Tree shaking'in çalışması için bazı koşullar gerekir. Kütüphaneler ES module formatında yazılmış olmalıdır; CommonJS (require()) formatı tree shaking için uygun değildir. Side effect olan modüller işaretlenmişse (sideEffects: false package.json'da) derleme aracı daha agresif biçimde ayıklama yapabilir. Dinamik import kullanan kod ise analiz edilmesi zor olduğundan bazı durumlarda tree shaking dışında kalır.

Lodash kullanıyorsanız import _ from 'lodash' yerine import debounce from 'lodash/debounce' şeklinde fonksiyon bazlı import tercih edin. Moment.js gibi büyük kütüphaneler için Day.js gibi daha küçük alternatifleri değerlendirin. Her bağımlılık eklenirken "gerçekten ihtiyacım var mı?" sorusunu sormak, bundle boyutunu uzun vadede kontrol altında tutar.

  • ES module kullanımı: import/export sözdizimi tree shaking için şarttır; CommonJS modülleri daha zor analiz edilir.
  • Fonksiyon bazlı import: Büyük kütüphanelerden yalnızca kullanılan fonksiyonu import edin; tüm paketi dahil etmekten kaçının.
  • Alternatif kütüphaneler: moment.js (67KB) yerine day.js (2KB), lodash (72KB) yerine native array methods kullanımını değerlendirin.
  • Bundle analizi: Her release öncesi bundle boyutunu kontrol edin; yeni bağımlılık eklenmesi otomatik uyarı tetiklesin.

Script yükleme stratejisi seçmeden önce mevcut etkileşim gecikmesini ölçün

Dağılıma bakın; tek sayı yanıltır. Mobil tarafta düşük işlem gücü ve ağ dalgalanması masaüstünde görünmeyen gecikmeleri büyütebilir.

  • Gerçek kullanıcı verisi: CrUX raporundan 75. yüzdelik dilimi kontrol edin, ortalamaya değil.
  • Laboratuvar testi: Lighthouse'u 3G throttling ile çalıştırın, WiFi değil.
  • Cihaz kırılımı: Düşük-orta-yüksek segment için ayrı eşik belirleyin.
  • Zaman karşılaştırması: Haftalık trend grafiği tutun, tek ölçüm yanıltır.

Bloklayan script, ağır medya ve ana iş parçacığı yükünü birbirinden ayırın

Script darboğazını ayırırken render blocking kaynak yaklaşımı ve critical CSS planı birlikte ele alınırsa ilk ekran daha tutarlı hızlanır.

Chrome DevTools Performance sekmesinde Main thread grafiğine bakın: sarı bloklar uzunsa JavaScript işlem yükü yüksektir. Network sekmesinde waterfall grafiğinde render-blocking kaynakları tespit edin. Lighthouse raporunda "Opportunities" bölümü milisaniye bazında kazanç potansiyeli gösterir.

  • Ağır medya: 1MB üzeri görsel LCP'yi 2 saniye geciktirebilir, WebP + lazy loading kullanın.
  • Bloklayan kaynak: head içindeki CSS ve JS render'ı bloklar, critical CSS inline yapın.
  • Ana iş parçacığı yükü: 50ms üzeri Long Task varsa kod splitting uygulayın.
  • Sunucu gecikmesi: TTFB 600ms üzerindeyse CDN veya sunucu optimizasyonu öncelikli.

Script erteleme kararını etki-maliyet matrisine göre önceliklendirin

Etki büyüklüğü, uygulama maliyeti, risk seviyesi ve geri dönüş süresi dört temel kriter. Görsel optimizasyonu yüksek etki + düşük risk, sunucu mimarisi değişikliği yüksek etki + yüksek risktir. Önce yüksek etki + düşük maliyet adımları uygulanır.

  • Etki büyüklüğü: Lighthouse "Opportunities" bölümünde milisaniye kazancına bakın, 500ms+ kazanç öncelikli.
  • Uygulama maliyeti: Geliştirici-gün cinsinden hesaplayın, 2 günden az iş hızlı kazanç sağlar.
  • Risk seviyesi: Kritik iş akışını etkileyen değişiklikler A/B test ile yayınlanmalı.
  • Geri dönüş süresi: Feature flag ile yayınlanan değişiklikler 5 dakikada geri alınabilir.

Defer/async değişikliklerini izole sayfa tipiyle test edin

Önce %5 trafikte test edin, metrik stabil kalırsa %25'e çıkarın, sorun yoksa %100'e açın. CI/CD pipeline'ına Lighthouse CI ekleyin; her commit'te skor düşerse build fail olsun. Feature flag kullanın, 5 dakikada eski versiyona dönebilmelisiniz.

  • Aşamalı yayın: %5 → %25 → %100 trafik artışı, her adımda 24 saat bekleyin.
  • Önce test sonra canlı: Staging ortamında Lighthouse skoru 90+ olmalı, yoksa canlıya almayın.
  • Regresyon kontrolü: CI/CD'ye Lighthouse CI ekleyin, skor 5 puan düşerse build fail.
  • Geri alma planı: Feature flag kullanın, sorun çıkarsa 5 dakikada geri alın.

Lab ortamı ideal koşulları simüle eder, alan verisi gerçek yükü gösterir

Sonuç kontrolünde Lighthouse raporu yorumlama ile sayfa yüklenme hızı hedefleri aynı tabloda izlendiğinde yanlış optimizasyon riski düşer.

Lab testi güçlü CPU, stabil ağ ve temiz cache ile çalışır. Gerçek kullanıcı düşük işlem gücü, dalgalı ağ ve arka planda 20 açık sekmeyle sayfanıza erişiyor. 75. yüzdelik dilime bakın; ortalama, hızlı kullanıcılar tarafından yukarı çekilir.

  • Tek ölçüm yanılgısı: 10 kez ölçün, medyan değeri alın, tek ölçüm %30 sapma gösterebilir.
  • Aşırı ortalama kullanımı: 75. yüzdelik dilim gerçek kullanıcı deneyimini yansıtır, ortalama değil.
  • Coğrafya farkı: Asya'dan ABD sunucusuna bağlanan kullanıcı 300ms ekstra gecikme yaşar.
  • Cihaz sınıfı etkisi: Düşük segment Android cihazlar iPhone 14'ten 4x yavaş JavaScript çalıştırır.

Bundle boyutu düzenli izlenmezse yükleme süresi sessizce artar

CrUX raporunu her Pazartesi kontrol edin; 75. yüzdelik dilim eşiğin üzerine çıktıysa nedenini araştırın. CI/CD pipeline'ına Lighthouse CI ekleyin, skor düşerse merge bloklansın. Her iyileştirmeyi kaydedin: tarih, değişiklik, metrik sonucu.

  • Haftalık denetim: CrUX raporunu her Pazartesi kontrol edin, 75. yüzdelik dilim eşiğin üzerine çıktıysa alarm verin.
  • Yayın öncesi kontrol: CI/CD'ye Lighthouse CI ekleyin, skor 5 puan düşerse merge bloklansın.
  • Otomatik alarm: Sayfa yüklenme süresi 3 saniyeyi geçerse Slack'e bildirim gitsin.
  • Dokümantasyon disiplini: Her iyileştirmeyi wiki'ye kaydedin, 6 ay sonra aynı sorunu tekrar çözmeyin.

Bir ürün liste sayfasında kullanıcı filtreye tıkladığında gecikme hissediliyordu. İncelemede kritik olmayan scriptlerin açılışta ana iş parçacığını meşgul ettiği görüldü. Ekip zorunlu olmayan parçaları defer/async stratejileriyle yeniden konumlandırdı ve ilk yük baskısını azalttı.

Etkileşim daha erken kullanılabilir hale geldi. Doğru sıra, çoğu zaman dosya sayısından daha belirleyicidir.

JavaScript tarafında en güçlü kazanım, hangi kodun ne zaman çalışacağını bilinçli planlamaktan gelir.