Modern bir web projesinde JavaScript bundle boyutu, çoğu zaman kimsenin bilinçli bir karar vermediği halde yavaş yavaş büyür. Bir tarih kütüphanesi eklenir, bir grafik paketi gelir, ikon seti içeri alınır ve altı ay sonra ana paketiniz 900 KB'a ulaşmıştır. Bu boyutun bedeli sadece indirme süresi değildir; tarayıcı o JavaScript'i ayrıştırmak, derlemek ve çalıştırmak zorundadır ve bu işlem orta seviye bir Android telefonda masaüstünün üç-dört katı sürer. Sonuç, kullanıcının ekranda içerik gördüğü ama hiçbir tıklamanın yanıt vermediği o rahatsız edici saniyelerdir.
Bu rehberde bundle boyutunu önce nasıl doğru ölçeceğini, boyutun nereden geldiğini nasıl bulacağını, en büyük kazancı hangi hamlelerin verdiğini (spoiler: minify değil, bağımlılık seçimi) ve büyümenin tekrar başlamaması için nasıl bir bütçe kuracağını anlatacağım. Örnekler bundler'dan bağımsız; webpack, Vite, Rollup ya da esbuild kullanıyor olman fark etmez.
Boyut Neden Bu Kadar Önemli#
Bir JavaScript dosyasının maliyeti üç aşamalıdır ve çoğu kişi yalnızca ilkini düşünür. İndirme ağ hızına bağlıdır ve sıkıştırma sayesinde en ucuz aşamadır. Ayrıştırma ve derleme dosyanın sıkıştırılmamış boyutuna bağlıdır; brotli ile 200 KB'a inen bir dosya bellekte hâlâ 800 KB'lık koddur ve CPU onu baştan sona işlemek zorundadır. Çalıştırma ise en pahalı aşamadır: modülleri değerlendirir, bileşenleri kurar, olay dinleyicilerini bağlar.
Bu üçünün toplamı doğrudan Total Blocking Time ve Interaction to Next Paint metriklerine yansır. Kullanıcı sayfayı görür ama menüye tıkladığında hiçbir şey olmaz, çünkü ana iş parçacığı hâlâ bundle'ı çalıştırmaktadır. İşte "site açıldı ama takılıyor" şikayetinin teknik karşılığı budur.
| Boyut (sıkıştırılmış) | Tipik ayrıştırma+çalıştırma süresi (orta seviye telefon) | Değerlendirme |
|---|---|---|
| 50 KB altı | 100 ms'nin altında | Çok iyi |
| 50-150 KB | 150-400 ms | Kabul edilebilir |
| 150-350 KB | 400 ms - 1 s | Etkileşim gecikir |
| 350 KB üstü | 1 s ve üzeri | Kullanıcı fark eder |
Bu tablodaki süreler cihazdan cihaza değişir, ama büyüklük sırası her ölçümde aynı çıkar. Hedefin, ilk yüklemede çalışan JavaScript'i mümkün olduğunca küçük tutmaktır — ilerideki bölümlerde göreceğin gibi, bu her zaman "kodu sil" anlamına gelmez.
Önce Ölç: Bundle Analizi#
Tahminle optimizasyon yapmak zaman kaybıdır. Hemen her bundler için bir görselleştirme aracı vardır ve hepsi aynı soruyu cevaplar: bu 700 KB'ın hangi modülden geldiği.
# Vite / Rollup için
npm install --save-dev rollup-plugin-visualizer
# webpack için
npm install --save-dev webpack-bundle-analyzer
# Kurulum olmadan hızlı bir bakış: çıktı dosyalarının boyutları
ls -lhS dist/assets/*.js | head -10
Vite kullanıyorsan yapılandırma birkaç satırdır:
// vite.config.js
import { visualizer } from "rollup-plugin-visualizer";
export default {
plugins: [
visualizer({ filename: "dist/stats.html", gzipSize: true, brotliSize: true }),
],
};
Build sonrası dist/stats.html dosyasını tarayıcıda açtığında, her modülün kapladığı alanı oransal olarak görürsün. Deneyimime göre bu haritada hemen her projede en az bir sürpriz çıkar: moment gibi tüm dil dosyalarını içeri alan bir tarih kütüphanesi, tek bir ikon için içeri alınmış koca bir ikon paketi ya da hem CommonJS hem ESM sürümü iki kez paketlenmiş bir bağımlılık. Bu haritayı okumadan atacağın her adım kör atıştır.
En Büyük Kazanç: Bağımlılık Seçimi#
Minify ve sıkıştırma önemlidir ama kazançları yüzdesel olarak sınırlıdır. Asıl fark, hangi kütüphaneyi kullandığın kararında yatar. Aynı işi yapan iki paket arasında beş-on kat boyut farkı olabilir.
En sık karşılaştığım üç örnek şunlardır. Birincisi tarih işlemleri: tüm yerelleştirmeleriyle gelen ağır bir tarih kütüphanesi yerine modüler olanları ya da tarayıcının yerleşik Intl.DateTimeFormat API'sini kullanmak çoğu ihtiyacı sıfır bayt maliyetle karşılar. İkincisi yardımcı fonksiyon kütüphaneleri: lodash'in tamamını içeri almak yerine yalnızca kullandığın fonksiyonu almak ya da modern JavaScript'in yerleşik dizi metotlarına geçmek büyük fark yaratır. Üçüncüsü ikonlar: bir ikon setinin tamamını içeri alan bir import satırı, tek bir ikon için yüz kilobaytlar getirebilir.
// KÖTÜ: paketin tamamı grafiğe girer
import _ from "lodash";
import * as icons from "ikon-paketi";
// İYİ: yalnızca gerçekten kullanılan modüller
import debounce from "lodash/debounce";
import { ChevronDown } from "ikon-paketi/ChevronDown";
// EN İYİ: yerleşik API yeterliyse bağımlılığı hiç ekleme
const tarih = new Intl.DateTimeFormat("tr-TR", { dateStyle: "long" }).format(new Date());
İkinci import biçiminin işe yaraması için paketin ESM sürümü sunması ve bundler'ının ölü kodu ayıklayabilmesi gerekir; bu mekanizmanın nasıl çalıştığını ve neden bazen çalışmadığını tree shaking yazısında ayrıntılı anlattım. Yeni bir bağımlılık eklemeden önce ise şu soruyu sorma alışkanlığı, sonradan yapacağın tüm optimizasyonlardan daha değerlidir: bu paketin bana kazandırdığı süre, kullanıcının indireceği baytlara değer mi?
Minify, Sıkıştırma ve Transfer Boyutu#
Bu iki aşama farklı şeylerdir ve sırayla uygulanır. Minify, kodun kendisini küçültür: boşluklar silinir, değişken adları kısaltılır, ölü dallar atılır. Modern bundler'ların üretim modunda bu zaten açıktır; esbuild veya terser arka planda çalışır. Yapman gereken tek şey build'i gerçekten üretim modunda çalıştırdığından emin olmaktır.
Sıkıştırma ise sunucunun işidir ve dosyayı ağdan geçerken küçültür. Gzip yaygındır, brotli daha iyidir; JavaScript metni üzerinde brotli genellikle gzip'ten yüzde 15-20 daha iyi sonuç verir.
# Nginx: brotli varsa öncelik onda, yoksa gzip
brotli on;
brotli_comp_level 6;
brotli_types application/javascript text/css application/json image/svg+xml;
gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types application/javascript text/css application/json image/svg+xml;
Apache/LiteSpeed tarafında aynı işi .htaccess üzerinden yapabilirsin; ayrıntılar için htaccess önbellek ve sıkıştırma yazısına bakabilirsin. Sıkıştırmanın gerçekten çalışıp çalışmadığını doğrulamak tek komutluk bir iştir:
# Yanıt başlığında content-encoding görünmeli
curl -sI -H "Accept-Encoding: br,gzip" https://firmaniz.com/assets/app.js | grep -i "content-encoding\|content-length"
Buradaki kritik ayrım şudur: sıkıştırma ağ maliyetini düşürür, ayrıştırma ve çalıştırma maliyetini düşürmez. Yani brotli açmak sitenizi hissedilir biçimde hızlandırabilir ama 900 KB'lık bir bundle'ın CPU maliyetini ortadan kaldırmaz. O maliyeti düşürmenin tek yolu, gerçekten daha az kod çalıştırmaktır.
Az Kod Çalıştırmak: Bölme ve Erteleme#
Bundle'ı küçültmenin en etkili yolu, kodu silmek değil ilk yüklemeden çıkarmaktır. Kullanıcı ana sayfaya girdiğinde yönetim panelinin, zengin metin editörünün veya ödeme adımının koduna ihtiyacı yoktur. Bu kodları ayrı parçalara bölüp gerektiğinde yüklemek, ilk yüklemede çalışan JavaScript'i yarıya indirebilir.
// Rota bazlı bölme (React örneği)
const YonetimPaneli = lazy(() => import("./sayfalar/YonetimPaneli"));
// Etkileşim bazlı bölme: editör yalnızca kullanıcı yazmaya başlayınca insin
async function editoruAc(hedef) {
const { Editor } = await import("./bilesenler/Editor");
new Editor(hedef).mount();
}
Bu tekniğin tüm ayrıntıları ve tuzakları için code splitting yazısına bak. Bölmeyle birlikte yüklenen parçaların çalışma anını da doğru ayarlamak gerekir; defer ve async niteliklerinin bu tabloya nasıl oturduğunu defer ve async farkı yazısında bulabilirsin.
Üçüncü parti script'leri de unutma. Chat widget'ı, ısı haritası aracı, sosyal medya yerleştirmeleri — bunlar senin bundle'ında görünmez ama kullanıcının indirdiği ve çalıştırdığı JavaScript'e dahildir. Çoğu zaman en büyük tek kalem bunlardır ve çözümleri de basittir: gerçekten gerekli olup olmadığını sorgula, gerekliyse kullanıcı etkileşime girene kadar yükleme.
Bütçe Koymak ve Büyümeyi Durdurmak#
Tek seferlik temizlik yaptıysan tebrikler, ama üç ay sonra aynı yerdesin. Kalıcı çözüm, boyutu bir sözleşmeye bağlamaktır. En basit hali, build çıktısını bir eşikle karşılaştıran birkaç satırlık kontroldür:
#!/usr/bin/env bash
# scripts/bundle-butcesi.sh
LIMIT_KB=180
TOPLAM=$(gzip -c dist/assets/*.js | wc -c)
TOPLAM_KB=$((TOPLAM / 1024))
echo "Toplam JS (gzip): ${TOPLAM_KB} KB / limit ${LIMIT_KB} KB"
if [ "$TOPLAM_KB" -gt "$LIMIT_KB" ]; then
echo "Bundle bütçesi aşıldı."
exit 1
fi
Bu script'i npm run build sonrasına ve sürekli tümleştirme akışına bağladığında, boyutu büyüten değişiklik altı ay sonra bir kullanıcı şikayetiyle değil, aynı gün kırmızı bir build ile önüne gelir. Bütçe rakamını gerçekçi seç: bugünkü boyutunun biraz üstünü limit yap, sonra zamanla aşağı çek. Ulaşılamayacak bir hedef koyup script'i devre dışı bırakmak, hiç koymamaktan daha kötüdür.
İkinci Ziyaretin Maliyeti: Önbellek ve Sürümleme#
Bundle boyutu tartışmasının çoğu ilk ziyaret üzerinden yürür, oysa bir ziyaretçinin sitede geçirdiği zamanın büyük kısmı ikinci ve sonraki sayfalardadır. Burada belirleyici olan boyut değil, dosyanın önbellekten gelip gelmediğidir. Doğru kurulum ikili bir kuraldır: JavaScript dosyalarının adı içerik özetini taşısın ve o dosyalar çok uzun süre önbelleklensin; HTML belgesi ise hiç önbelleklenmesin ya da çok kısa süre önbelleklensin.
Bu ayrımın sebebi basit. İçeriği değişen bir dosyanın adı da değiştiği için tarayıcının eski sürümü sunma riski yoktur; dolayısıyla bir yıllık önbellek tamamen güvenlidir. HTML ise hangi dosya adının geçerli olduğunu söyleyen tek yerdir, o yüzden her zaman taze gelmelidir.
# İçerik özetli varlıklar: uzun ve değişmez önbellek
location ~* ^/assets/.+\.(js|css|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# HTML belgesi: her zaman taze
location = /index.html {
add_header Cache-Control "no-cache";
}
Modern bundler'lar dosya adına özet damgasını varsayılan olarak ekler; senin işin sunucu tarafında bu başlıkların gerçekten gönderildiğini doğrulamaktır. Tek satırlık bir kontrol yeterli:
curl -sI https://firmaniz.com/assets/app.a1b2c3d4.js | grep -i "cache-control\|etag"
Burada max-age=31536000 yerine birkaç saatlik bir değer görürsen, kullanıcıların her ziyarette aynı JavaScript'i yeniden indirdiğini bilirsin — ve bu, bundle'ı yüz kilobayt küçültmekten çok daha büyük bir kayıptır. Aynı mantığın diğer statik dosyalar için kurulumunu cPanel önbellek yöneticisi yazısında paylaşımlı barındırma tarafından da görebilirsin.
Sık Yapılan Hatalar#
Geliştirme build'iyle ölçmek. Geliştirme modundaki paketler kaynak haritaları, uyarı mesajları ve sıcak yeniden yükleme kodu içerir; üretim build'inin iki-üç katı olabilir. Ölçümü her zaman npm run build çıktısı üzerinden yap.
Sıkıştırılmış boyutu tek gerçek sanmak. "Gzip'ten sonra 90 KB, sorun yok" cümlesi yanıltıcıdır; CPU maliyeti sıkıştırılmamış boyuta bağlıdır. Her iki sayıyı birlikte takip et.
Polyfill'leri her tarayıcıya göndermek. Çok eski tarayıcılar için üretilen dönüşümler ve polyfill'ler, modern tarayıcı kullanan yüzde doksan beşlik kesime gereksiz yük bindirir. Hedef tarayıcı listeni gerçekçi tut.
Aynı kütüphanenin iki sürümünü paketlemek. Bağımlılıklardan biri eski bir sürümü, diğeri yenisini isteyince ikisi de bundle'a girer. Analiz haritasında aynı paketin iki kez görünmesi bunun işaretidir; npm ls paket-adi ile teşhis edilir.
Kaynak haritalarını üretime yüklemek. Kullanıcı indirmez ama sunucuda yer kaplar ve kodunu açık eder. Üretimde kapalı tutmak ya da yalnızca hata izleme servisine yüklemek doğru yaklaşımdır.
Sıkça Sorulan Sorular#
JavaScript bundle boyutu ne kadar olmalı#
Kesin bir rakam yoktur ama pratik bir hedef, ilk yüklemede çalışan JavaScript'in sıkıştırılmış halde 150 KB'ın altında kalmasıdır. Bunun üzerine çıktığında orta seviye telefonlarda etkileşim gecikmesi ölçülebilir hale gelir. Karmaşık uygulamalarda bu rakamı tutmanın yolu kod silmek değil, ilk yüklemeye girmeyen parçaları bölmektir.
Minify yapmak yeterli mi#
Yeterli değil ama zorunlu. Minify tipik olarak yüzde 30-40 civarında bir küçülme sağlar ve modern bundler'larda üretim modunda zaten otomatiktir. Asıl büyük kazançlar bağımlılık seçiminde, ölü kod ayıklamada ve kod bölmede yatar. Minify'ı bir temel hijyen adımı olarak düşün, optimizasyon stratejin olarak değil.
Bundle boyutunu nasıl analiz ederim#
Bundler'ına uygun bir görselleştirme eklentisi kurup build sonrası üretilen haritayı tarayıcıda açman en pratik yoldur; Vite için rollup-plugin-visualizer, webpack için webpack-bundle-analyzer yaygın seçeneklerdir. Harita, her modülün kapladığı alanı oransal olarak gösterir ve genellikle beklemediğin bir paketin en büyük dilim olduğunu ortaya çıkarır. Hiçbir kurulum yapmadan başlamak istersen ls -lhS dist/assets/*.js komutu bile büyükten küçüğe bir liste verir.
Brotli mi gzip mi kullanmalıyım#
İkisini birlikte sunmak en doğrusudur: brotli destekleyen tarayıcılara brotli, desteklemeyenlere gzip gider. JavaScript ve CSS gibi metin dosyalarında brotli genellikle gzip'ten belirgin biçimde daha iyi sıkıştırır. Sunucu tarafında iki modülü de etkinleştirip içerik türlerini doğru listelemen yeterlidir; tarayıcı Accept-Encoding başlığıyla hangisini desteklediğini zaten bildirir.
Kütüphaneleri CDN'den çekmek bundle'ı küçültür mü#
Dosyanı küçültür ama kullanıcının indirdiği toplam baytı azaltmaz; sadece başka bir alan adına taşır. Üstelik ayrı bir alan adı yeni bir DNS, TCP ve TLS turu demektir ve tarayıcıların önbellek bölümlemesi nedeniyle "başka site zaten indirmiştir" varsayımı artık geçerli değildir. Genel olarak kütüphaneleri kendi alan adından, sürüm damgalı ve uzun önbellekli servis etmek daha hızlı sonuç verir.
Bundle küçülünce sayfa hızı ne kadar artar#
Kazanç en çok etkileşim metriklerinde görünür. 600 KB'lık bir bundle'ı 200 KB'a indirdiğinde orta seviye bir telefonda ana iş parçacığının bloke olduğu süre birkaç yüz milisaniye kısalır ve sayfa "tıklamaya cevap veriyor" hale çok daha erken gelir. Görsel yüklenme metriklerindeki iyileşme daha ılımlıdır, çünkü onları esas belirleyen sunucu yanıt süresi ve CSS'tir.
Kapanış#
Bundle boyutuyla mücadele, tek bir sihirli ayarla değil birkaç alışkanlıkla kazanılır: her zaman üretim build'i üzerinden ve görselleştirme aracıyla ölç, yeni bir bağımlılık eklemeden önce maliyetini sorgula, sunucu tarafında brotli/gzip'in gerçekten çalıştığını doğrula ve en önemlisi ilk yüklemede çalışması gerekmeyen kodu bölerek dışarı çıkar. Bunların üzerine bir bütçe script'i koyduğunda, boyut bir daha sessizce büyüyemez.
İyi optimize edilmiş bir bundle'ın kazandırdığı süreyi yavaş bir sunucu geri alabilir; ilk baytın hızlı gelmesi ve statik dosyaların doğru başlıklarla servis edilmesi bu tablonun diğer yarısıdır. HTTP/2 ve brotli desteğiyle gelen web hosting paketlerimiz bu tabanı sağlar; kendi Node.js build sürecini çalıştırmak istersen tam root erişimli VDS veya bulut sunucu seçeneklerimize bakabilir, yapılandırmayı bize bırakmak istersen sunucu yönetimi hizmetimizden yararlanabilirsin.