İçeriğe geç
react

React Code Splitting Stratejileri

Tarık Tunç
React Code Splitting Stratejileri

React Code Splitting Stratejileri

Modern React uygulamaları büyüdükçe, JavaScript bundle boyutu da artar ve sayfa yüklenme süreleri uzar. Code splitting, uygulamanızı daha küçük parçalara bölerek sadece ihtiyaç duyulan kodu yüklemenizi sağlar. Bu rehberde, React'te code splitting'in farklı stratejilerini, Webpack ve Vite yapılandırmalarını ve gerçek dünya optimizasyon tekniklerini adım adım ele alacağız.

Code Splitting Nedir?

Code splitting, tek bir büyük JavaScript dosyası yerine birden fazla küçük dosya (chunk) oluşturma tekniğidir. Kullanıcı uygulamaya girdiğinde yalnızca o anda gerekli olan chunk yüklenir, geri kalanı ihtiyaç duyulduğunda lazy olarak çekilir. Bu, özellikle Time to Interactive (TTI) ve Largest Contentful Paint (LCP) metriklerini iyileştirir.

1. Route Bazlı Code Splitting

En yaygın ve etkili strateji, her route'u ayrı bir chunk'a ayırmaktır. Kullanıcı bir sayfaya ilk kez gittiğinde o sayfanın kodu yüklenir.

import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route, Link, useLocation } from 'react-router-dom';

// Her route ayrı bir chunk olarak yüklenir
const Home = lazy(() => import(/* webpackChunkName: "home" */ './pages/Home'));
const Products = lazy(() => import(/* webpackChunkName: "products" */ './pages/Products'));
const ProductDetail = lazy(() => import(/* webpackChunkName: "product-detail" */ './pages/ProductDetail'));
const Cart = lazy(() => import(/* webpackChunkName: "cart" */ './pages/Cart'));
const Checkout = lazy(() => import(/* webpackChunkName: "checkout" */ './pages/Checkout'));
const Admin = lazy(() => import(/* webpackChunkName: "admin" */ './pages/Admin'));

// Route bazlı loading component
function RouteLoader() {
  return (
    <div className="flex items-center justify-center min-h-[400px]">
      <div className="text-center">
        <div className="animate-spin rounded-full h-10 w-10 border-b-2 border-blue-600 mx-auto" />
        <p className="mt-3 text-gray-500">Sayfa yükleniyor...</p>
      </div>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <nav className="bg-white shadow p-4">
        <Link to="/" className="mr-4">Ana Sayfa</Link>
        <Link to="/products" className="mr-4">Ürünler</Link>
        <Link to="/cart" className="mr-4">Sepet</Link>
      </nav>

      <Suspense fallback={<RouteLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/products" element={<Products />} />
          <Route path="/products/:id" element={<ProductDetail />} />
          <Route path="/cart" element={<Cart />} />
          <Route path="/checkout" element={<Checkout />} />
          <Route path="/admin/*" element={<Admin />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

2. Component Bazlı Code Splitting

Ağır componentler (editörler, grafikler, haritalar) için component seviyesinde lazy loading uygulayabilirsiniz.

import { lazy, Suspense, useState } from 'react';

// Ağır kütüphaneler sadece gerektiğinde yüklenir
const RichTextEditor = lazy(() => import('./components/RichTextEditor'));
const ChartDashboard = lazy(() => import('./components/ChartDashboard'));
const MapView = lazy(() => import('./components/MapView'));

function ProductPage() {
  const [showEditor, setShowEditor] = useState(false);
  const [activeTab, setActiveTab] = useState<'info' | 'chart' | 'map'>('info');

  return (
    <div>
      <h1>Ürün Detayları</h1>

      {/* Editör sadece butona tıklanınca yüklenir */}
      <button onClick={() => setShowEditor(true)}>Açıklamayı Düzenle</button>
      {showEditor && (
        <Suspense fallback={<div className="h-64 bg-gray-100 animate-pulse rounded" />}>
          <RichTextEditor />
        </Suspense>
      )}

      {/* Tab bazlı lazy loading */}
      <div className="flex gap-2 mt-6">
        <button onClick={() => setActiveTab('info')}>Bilgi</button>
        <button onClick={() => setActiveTab('chart')}>Grafikler</button>
        <button onClick={() => setActiveTab('map')}>Harita</button>
      </div>

      <Suspense fallback={<div className="h-96 bg-gray-100 animate-pulse rounded mt-4" />}>
        {activeTab === 'chart' && <ChartDashboard />}
        {activeTab === 'map' && <MapView />}
      </Suspense>
    </div>
  );
}
İpucu: Webpack magic comments ile chunk isimlerini belirleyebilirsiniz: import(/* webpackChunkName: "editor" */ './Editor'). Bu, DevTools Network sekmesinde hangi chunk'ın yüklendiğini kolayca görmenizi sağlar.

3. Prefetch ve Preload Stratejileri

import { lazy, Suspense, useCallback } from 'react';

// Lazy component tanımları
const CheckoutPage = lazy(() => import('./pages/Checkout'));
const AdminPage = lazy(() => import('./pages/Admin'));

// Prefetch fonksiyonu — idle zamanında yükle
function prefetchComponent(importFn: () => Promise<any>) {
  // requestIdleCallback ile tarayıcı boştayken yükle
  if ('requestIdleCallback' in window) {
    requestIdleCallback(() => importFn());
  } else {
    setTimeout(() => importFn(), 200);
  }
}

function CartSummary() {
  // Kullanıcı sepeti görüntülüyorsa, muhtemelen checkout'a gidecektir
  // Bu nedenle checkout sayfasını önceden yükle
  const handleMouseEnter = useCallback(() => {
    prefetchComponent(() => import('./pages/Checkout'));
  }, []);

  return (
    <div>
      <h2>Sepet Özeti</h2>
      <p>Toplam: 450 TL</p>
      <a
        href="/checkout"
        onMouseEnter={handleMouseEnter}
        onFocus={handleMouseEnter}
        className="bg-green-600 text-white px-6 py-3 rounded inline-block"
      >
        Ödemeye Geç
      </a>
    </div>
  );
}

// Vite ile prefetch: link tag'ı ekleme
function addPrefetchLink(href: string) {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = href;
  document.head.appendChild(link);
}

4. Bundle Analizi

// Vite için bundle analizi
// npm install -D rollup-plugin-visualizer

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    react(),
    visualizer({
      filename: './dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Vendor chunk'ları ayır
          'react-vendor': ['react', 'react-dom'],
          'router': ['react-router-dom'],
          'ui': ['framer-motion', '@radix-ui/react-dialog'],
          'query': ['@tanstack/react-query'],
        },
      },
    },
  },
});
Uyarı: Aşırı code splitting yapmaktan kaçının. Her chunk bir HTTP isteği gerektirir. Çok küçük chunk'lar (5KB altı), HTTP overhead nedeniyle performansı iyileştirmek yerine kötüleştirebilir. Dengeli bir yaklaşım benimseyin.

Performans Metrikleri

Metrik Code Splitting Öncesi Code Splitting Sonrası
İlk Bundle Boyutu ~500 KB ~120 KB
Time to Interactive ~4.5s ~1.8s
LCP ~3.2s ~1.5s
Bilgi: HTTP/2 multiplexing sayesinde, birden fazla küçük dosyayı paralel olarak yüklemek artık performans sorunu değildir. Ancak yine de chunk sayısını makul tutmak önemlidir.

Sonuç

Code splitting, React uygulamalarının performansını iyileştirmenin en etkili yollarından biridir. Route bazlı splitting ile başlayın, ağır componentler için component bazlı splitting uygulayın, prefetch ile kullanıcı deneyimini optimize edin ve bundle analizi ile sonuçları ölçün. Unutmayın: Ölçemediğinizi optimize edemezsiniz.

Kaynaklar

İlgili Yazılar