Site Hızı & Performans

    Code Splitting Nedir

    JavaScript'i parçalara bölüp ilk yüklemede yalnızca gerekeni indirme yöntemi.

    10 dk okuma Güncellendi: 25 Ağustos 2026

    Bir web uygulamasının tüm JavaScript'ini tek bir dosyada göndermek, restorana giren müşteriye menüdeki her yemeği aynı anda servis etmeye benzer. Kullanıcı ana sayfaya girmiştir ama yönetim panelinin, ödeme adımının, grafik kütüphanesinin ve zengin metin editörünün kodunu da indirir, ayrıştırır ve çalıştırır. Code splitting tam olarak bunu çözer: kodu mantıklı parçalara böler ve her parçayı gerçekten gerektiği anda yükler.

    Bu yazıda dinamik import() ifadesinin nasıl çalıştığını, rota ve etkileşim bazlı bölmenin nasıl kurulduğunu, üçüncü parti kütüphaneleri ayrı bir parçaya almanın önbellek açısından neden mantıklı olduğunu ve bölmeyi aşırıya kaçırdığında ortaya çıkan şelale (waterfall) sorununu anlatacağım. Örnekler React üzerinden ama mantık Vue, Svelte ya da vanilla JavaScript için birebir aynıdır.

    Tek Parça Bundle'ın Problemi#

    Bundler'lar varsayılan olarak giriş noktandan başlayıp tüm import zincirini takip eder ve her şeyi tek bir çıktıya toplar. Bu, dağıtımı kolaylaştırdığı için uzun süre standart yaklaşımdı. Sorun, uygulama büyüdükçe bu tek dosyanın kullanıcının o an ihtiyaç duyduğu koddan kat kat büyük hale gelmesidir.

    Somutlaştıralım. Bir e-ticaret sitesinde ana sayfa için gereken kod belki 80 KB'dır; ama aynı bundle içinde ödeme formu doğrulaması, sipariş takip haritası, ürün yorumları düzenleyicisi ve yönetim paneli de vardır. Toplam 600 KB'lık dosyanın tamamı indirilir ve — daha önemlisi — tamamı ayrıştırılıp değerlendirilir. Kullanıcı hiç ödeme sayfasına gitmese bile o kodun maliyetini öder.

    Yaklaşımİlk yüklemede inenİlk yüklemede çalışanSayfa geçişi
    Tek parça bundleTamamıTamamıAnında (zaten yüklü)
    Rota bazlı bölmeOrtak + o rotaOrtak + o rotaKüçük bir istek
    Aşırı bölmeÇok sayıda küçük parçaAzZincirleme istekler, gecikme

    Ortadaki satır hedeftir. Üçüncü satır, bölmenin de kendi maliyeti olduğunu hatırlatır; buna aşağıda ayrıca değineceğim. Bölmeye başlamadan önce toplam boyutun nereden geldiğini görmek istersen bundle boyutunu küçültme yazısındaki analiz adımı doğal başlangıç noktasıdır.

    Dinamik import() Temeli#

    Code splitting'in tamamı tek bir dil özelliğine dayanır: dinamik import(). Statik import ifadesi dosyanın başında durur ve bundler onu derleme anında çözer. Dinamik import() ise bir fonksiyon çağrısı gibi çalışır, bir Promise döner ve bundler onu gördüğü yerde bölme noktası oluşturur.

    // Statik: bu modül ana parçaya girer
    import { pdfOlustur } from "./pdf.js";
    
    // Dinamik: ayrı bir parçaya alınır, çağrıldığında iner
    async function faturayiIndir(veri) {
      const { pdfOlustur } = await import("./pdf.js");
      return pdfOlustur(veri);
    }
    

    İkinci biçimde bundler pdf.js ve onun bağımlılıklarını ayrı bir dosyaya koyar. Kullanıcı fatura indirme düğmesine basmadan o dosya hiç istenmez. Kritik nokta şudur: bundler'ın bölme yapabilmesi için import() içindeki yolun statik olarak analiz edilebilir olması gerekir. Tamamen değişkenden gelen bir yol, bundler'ın neyi paketleyeceğini bilememesine yol açar:

    // SORUNLU: bundler hangi dosyaları paketleyeceğini bilemez
    const modul = await import(kullanicininVerdigiYol);
    
    // GÜVENLİ: desen belli, bundler eşleşen tüm dosyaları ayrı parçalara alır
    const modul = await import(`./diller/${dilKodu}.js`);
    

    İkinci biçimde bundler ./diller/ altındaki tüm dosyalar için birer parça üretir ve çalışma anında doğru olanı yükler. Bu, dil dosyaları ve tema varyantları için ideal bir kalıptır.

    Rota Bazlı Bölme#

    En yüksek getirili ve en kolay uygulanan bölme türü rota bazlıdır: her sayfa kendi parçasında durur. Kullanıcı ana sayfaya girdiğinde yalnızca ana sayfanın kodu iner; başka bir sayfaya geçtiğinde o sayfanın parçası istenir.

    import { lazy, Suspense } from "react";
    import { Routes, Route } from "react-router-dom";
    
    const AnaSayfa   = lazy(() => import("./sayfalar/AnaSayfa"));
    const UrunListesi = lazy(() => import("./sayfalar/UrunListesi"));
    const Odeme      = lazy(() => import("./sayfalar/Odeme"));
    const Yonetim    = lazy(() => import("./sayfalar/Yonetim"));
    
    export default function App() {
      return (
        <Suspense fallback={<div className="iskelet" />}>
          <Routes>
            <Route path="/" element={<AnaSayfa />} />
            <Route path="/urunler" element={<UrunListesi />} />
            <Route path="/odeme" element={<Odeme />} />
            <Route path="/yonetim" element={<Yonetim />} />
          </Routes>
        </Suspense>
      );
    }
    

    Suspense bileşenine verdiğin fallback, parça inerken görünen şeydir ve önemsiz değildir. Boş bir div koyarsan sayfa geçişinde ekran bir an bomboş kalır; bunun yerine gerçek düzeni taklit eden bir iskelet koyarsan hem algılanan hız artar hem de düzen kayması olmaz. Bu ayrıntının Core Web Vitals tarafındaki karşılığını düzen kaymasını düzeltme yazısında bulabilirsin.

    Bir uyarı: yönetim paneli gibi çok az kullanıcının gördüğü ağır rotaları bölmek en büyük kazancı verir. Buna karşılık ana sayfayı lazy yapmak genellikle zararlıdır, çünkü kullanıcıların çoğunun ilk gördüğü yerdir ve gereksiz bir istek turu eklersin.

    Etkileşim ve Görünürlük Bazlı Bölme#

    Rota bazlı bölmeden sonraki kazanç, aynı sayfa içindeki ağır bileşenleri ayırmaktan gelir. Harita, grafik, video oynatıcı, zengin metin editörü, tarih seçici — bunlar tipik olarak büyüktür ve kullanıcıların önemli bir kısmı onlara hiç dokunmaz.

    // 1) Etkileşim tetikleyici: kullanıcı tıklayınca yükle
    document.querySelector("#grafik-ac").addEventListener("click", async () => {
      const { grafikCiz } = await import("./bilesenler/grafik.js");
      grafikCiz(document.querySelector("#grafik"));
    }, { once: true });
    
    // 2) Görünürlük tetikleyici: bileşen ekrana yaklaşınca yükle
    const gozlemci = new IntersectionObserver((girdiler) => {
      for (const girdi of girdiler) {
        if (!girdi.isIntersecting) continue;
        gozlemci.unobserve(girdi.target);
        import("./bilesenler/yorumlar.js").then((m) => m.yorumlariKur(girdi.target));
      }
    }, { rootMargin: "300px" });
    
    document.querySelectorAll("[data-gec-yukle]").forEach((el) => gozlemci.observe(el));
    

    İkinci örnekteki rootMargin: "300px" değeri işin püf noktasıdır: bileşen ekrana girmeden 300 piksel önce yüklemeyi başlatır, böylece kullanıcı oraya kaydırdığında içerik zaten hazırdır. Bu değeri sıfır bırakırsan kullanıcı bileşeni boş görür ve ardından dolar.

    Vendor Ayrımı ve Önbellek Stratejisi#

    Kendi kodun her yayında değişir, kullandığın kütüphaneler ise ayda bir bile değişmeyebilir. İkisi aynı dosyada durursa, tek satırlık bir düzeltme yaptığında kullanıcı 300 KB'lık kütüphane yığınını da yeniden indirir. Bu yüzden üçüncü parti bağımlılıkları ayrı bir parçaya almak, boyut değil önbellek optimizasyonudur.

    // vite.config.js — kaba ama etkili bir vendor ayrımı
    export default {
      build: {
        rollupOptions: {
          output: {
            manualChunks(id) {
              if (id.includes("node_modules")) return "vendor";
            },
          },
        },
      },
    };
    

    Burada dikkatli olman gereken bir tuzak var: manualChunks ile el yordamıyla bölme yapmak, kütüphaneler arasındaki başlatma sırasını bozabilir. Özellikle React gibi bir bağlam (context) kuran kütüphaneleri ayrı parçalara dağıtmak, "undefined okunamıyor" tarzı çalışma anı hatalarına yol açar. Bu yüzden ya tüm node_modules içeriğini tek bir vendor parçasında tut ya da hiç karışma; bundler'ın kendi otomatik bölmesi çoğu projede zaten makul sonuç verir. Ayrılan parçaların içerik damgalı adlarla ve uzun Cache-Control başlığıyla servis edilmesi de bu stratejinin ikinci yarısıdır.

    Pratik bir denge noktası şudur: vendor parçası, kendi uygulama kodunun birkaç katı büyüklüğe ulaşana kadar tek dosyada kalsın; ancak gerçekten devasa ve nadiren kullanılan bir kütüphane varsa (örneğin bir grafik ya da harita motoru) onu vendor içinde değil, kendi rota parçasında tut. Böylece ana sayfaya giren kullanıcı o kütüphaneyi hiç indirmez, ödeme ya da rapor sayfasına giden ise yalnızca bir kez indirir ve sonraki ziyaretlerde önbellekten alır.

    Bölmeyi Prefetch ile Tamamlamak#

    Bölmenin bedeli, parçanın gerektiği anda bir ağ turu beklemektir. Kullanıcı "Ödeme" düğmesine basar, parça inmeye başlar, bu arada ekranda iskelet durur. Bu gecikmeyi neredeyse tamamen ortadan kaldıran yöntem, parçayı kullanıcı isteyeceğini belli ettiğinde önceden indirmektir.

    // Fareyle üzerine gelindiğinde parçayı önceden getir
    const link = document.querySelector('a[href="/odeme"]');
    let getirildi = false;
    link.addEventListener("pointerenter", () => {
      if (getirildi) return;
      getirildi = true;
      import("./sayfalar/Odeme");   // sonucu kullanmıyoruz, sadece indiriyoruz
    });
    

    Aynı işi tarayıcıya link rel="prefetch" etiketiyle de yaptırabilirsin; hangi ipucunun ne zaman doğru olduğunu preload, prefetch ve preconnect farkı yazısında karşılaştırdım. Pratikte fare üzerine gelme (hover) tetikleyicisi, masaüstünde 200-300 ms'lik bir avans kazandırır ve bu genellikle parçanın tamamen inmesine yeter.

    Bölmenin Sonucunu Ölçmek#

    Bölme yaptıktan sonra "daha hızlı hissettiriyor" demek yeterli değil; bakman gereken üç somut sayı var. Birincisi ilk yüklemede inen JavaScript'in toplam boyutudur. DevTools'un Network sekmesinde JS filtresini seçip sayfayı önbelleksiz yüklediğinde alt çubuktaki transfer toplamını görürsün; bölme öncesi ve sonrası bu sayıyı aynı koşullarda karşılaştır.

    İkincisi ana iş parçacığının bloke olduğu süredir. Lighthouse raporundaki Total Blocking Time kalemi, bölmenin gerçek etkisini en iyi gösteren metriktir; çünkü indirmediğin kodu ayrıştırmaz ve çalıştırmazsın. Bölmenin işe yaradığı bir projede bu değer belirgin biçimde düşer, görsel yüklenme metrikleri ise daha ılımlı iyileşir.

    Üçüncüsü sayfa geçişlerindeki gecikmedir ve bu, bölmenin bedelidir. Bir rotadan diğerine geçerken parça inene kadar geçen süreyi ölçmezsen, ilk yüklemede kazandığını her tıklamada geri veriyor olabilirsin. Performance sekmesinde kayıt alırken bir rota geçişi yap ve isteğin ne kadar sürdüğüne bak; 100 ms'nin altındaki bir gecikme fark edilmez, 500 ms'yi bulan bir gecikme prefetch eklemen gerektiğinin işaretidir.

    # Üretim build'inde üretilen parçaları büyükten küçüğe listele
    ls -lhS dist/assets/*.js
    
    # Toplam ve en büyük parçanın sıkıştırılmış boyutu
    gzip -c dist/assets/*.js | wc -c
    

    Bu listede tek bir parçanın diğerlerinin toplamı kadar büyük olduğunu görüyorsan, bölme yapılmış ama asıl ağırlığı taşıyan modül hâlâ ortak parçadadır; genellikle her rotanın içeri aldığı bir yardımcı modül ya da bir tasarım sistemi bileşeni bunun sebebidir. Analiz haritasında o parçanın içine bakmak, sıradaki bölme noktasını doğrudan gösterir.

    Sık Yapılan Hatalar#

    Aşırı bölme. Her bileşeni ayrı parçaya almak, onlarca küçük dosya ve zincirleme istekler üretir. HTTP/2 ile paralel istekler ucuzdur ama bedava değildir; ayrıca her parçanın kendi çalışma maliyeti vardır. Bölme kararını "bu kod kullanıcıların yüzde kaçına gerekiyor" sorusuna göre ver.

    Şelale (waterfall) oluşturmak. A parçası indikten sonra B'yi, B indikten sonra C'yi isteyen bir zincir kurarsan, toplam gecikme tek tek sürelerin toplamı olur. Aynı anda gerekecek parçaları paralel başlat: await Promise.all([import("./a"), import("./b")]).

    Parça yükleme hatasını ele almamak. Yeni bir sürüm yayına alındığında eski parça dosyaları silinir; o sırada sayfayı açık tutan kullanıcı yeni bir rotaya geçmek isterse parça 404 döner ve uygulama beyaz ekrana düşer. Doğru çözüm, yükleme hatasını yakalayıp sayfayı bir kez yenilemektir.

    async function guvenliYukle(yukleyici) {
      try {
        return await yukleyici();
      } catch (hata) {
        // Bayat parça referansı: bir kez yenile
        if (!sessionStorage.getItem("parca_yenilendi")) {
          sessionStorage.setItem("parca_yenilendi", "1");
          location.reload();
        }
        throw hata;
      }
    }
    

    Yükleme durumunu görsel olarak boş bırakmak. Suspense fallback'ı boşsa sayfa geçişleri titrek görünür ve düzen kayar. Gerçek düzenin ölçülerini taşıyan bir iskelet kullan.

    Bölmeyi ölü kod ayıklamanın yerine koymak. Bölme, kodu daha sonraya erteler; hiç kullanılmayan kodu silmez. İkisi farklı işler yapar ve birlikte uygulanır; ayrıntısı tree shaking yazısında.

    Sıkça Sorulan Sorular#

    Code splitting her projede gerekli mi#

    Hayır. Toplam JavaScript'i sıkıştırılmış halde 100 KB'ın altında olan bir tanıtım sitesinde bölme yapmak, ek istekler ve karmaşıklık getirir ama hissedilir bir kazanç sağlamaz. Bölme, farklı kullanıcı gruplarının farklı kod yollarını kullandığı uygulamalarda anlamlıdır: yönetim paneli, ödeme akışı, ağır düzenleyici bileşenler gibi. Ölçüp karar vermek, varsayımla uygulamaktan her zaman daha iyidir.

    Rota bazlı bölme SEO'ya zarar verir mi#

    Arama motorları JavaScript'i çalıştırır ve dinamik olarak yüklenen parçaları da alır, dolayısıyla içerik doğru şekilde işlendiğinde dizine eklenir. Yine de içeriğin yalnızca JavaScript çalıştıktan sonra var olması, tarama bütçesi ve gecikme açısından dezavantajdır. Kritik içerik taşıyan sayfalarda sunucu tarafı işleme ya da önceden oluşturulmuş statik HTML kullanmak, bölmeden bağımsız olarak daha güvenli bir yaklaşımdır.

    Dinamik import değişken yol kabul eder mi#

    Tamamen serbest bir değişken kabul etmez; daha doğrusu kabul eder ama bundler o durumda hangi dosyaları paketleyeceğini bilemez ve parça üretemez. Çözüm, yolun sabit bir kısmını şablon dizesi içinde tutmaktır: import('./diller/' + kod + '.js') biçimi bundler'a bir desen verir ve eşleşen tüm dosyalar için ayrı parçalar üretilir. Böylece hem esneklik hem de statik analiz korunur.

    Kaç parça çok fazla sayılır#

    Kesin bir sayı yok ama pratik bir işaret şudur: tek bir sayfa açılışında ondan fazla ayrı JavaScript isteği görüyorsan muhtemelen aşırı bölmüşsündür. Özellikle 5 KB'ın altındaki parçalar, kendi istek maliyetlerini karşılamaz. Bu tür küçük parçaları birleştirmek, bundler yapılandırmasında genellikle bir minimum boyut eşiğiyle yapılabilir.

    Parça yükleme hatası nasıl çözülür#

    Bu hata neredeyse her zaman yeni bir sürümün yayına alınmasıyla eski parça dosyalarının silinmesinden kaynaklanır. Kullanıcının açık sekmesi hâlâ eski dosya adlarını hatırlar ve istediğinde 404 alır. En pratik çözüm, dinamik yükleme çağrılarını bir hata yakalayıcıyla sarmalayıp bir kereye mahsus sayfa yenilemesi tetiklemektir. Yenileme bayrağını oturum deposunda tutmak, sonsuz döngüyü engeller.

    Code splitting ile lazy loading aynı şey mi#

    Birbirine yakın ama aynı değil. Code splitting, kodun derleme anında ayrı dosyalara bölünmesidir. Lazy loading ise bu parçaların çalışma anında gerektiğinde yüklenmesidir. Pratikte ikisi birlikte kullanılır: bölme olmadan geç yükleyecek bir şey olmaz, geç yükleme olmadan da bölmenin faydası ortaya çıkmaz. Görseller için kullanılan "lazy loading" terimi ise tamamen ayrı bir konudur.

    Kapanış#

    Code splitting'in özü şudur: kullanıcıya o an gerekmeyen kodu göndermemek. Akılda tutulacak dört alışkanlık — önce rota bazlı bölmeyle başla, ağır ve az kullanılan bileşenleri etkileşim veya görünürlük tetikleyicisiyle ayır, üçüncü parti kütüphaneleri önbellek için ayrı bir parçada tut ve parça yükleme hatasına karşı bir yenileme yedeği bırak. Bölmeyi hover prefetch ile tamamladığında, kullanıcı bölmenin varlığını bile fark etmez.

    Bölmenin kazandırdığı milisaniyeleri, parçaları yavaş servis eden bir sunucu geri alır; her parça ayrı bir istek olduğu için HTTP/2, brotli ve doğru önbellek başlıkları burada normalden bile önemlidir. HTTP/2 destekli web hosting paketlerimiz bu tabanı sağlar, kendi build ve dağıtım sürecini kurmak istersen VDS veya bulut sunucu seçeneklerimiz uygundur; yapılandırmayı bizim üstlenmemizi istersen sunucu yönetimi hizmetimize bakabilirsin.

    JavaScriptBundlePerformans

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.