İçeriğe geç
react

React Suspense ve Lazy Loading: Performans Optimizasyonu

Tarık Tunç
React Suspense ve Lazy Loading: Performans Optimizasyonu

React Suspense ve Lazy Loading: Performans Optimizasyonu

Modern web uygulamalarında performans, kullanıcı deneyiminin en kritik unsurlarından biridir. React Suspense ve Lazy Loading, uygulamanızın ilk yüklenme süresini dramatik şekilde azaltmanızı sağlayan güçlü araçlardır. Bu rehberde, Suspense mekanizmasının nasıl çalıştığını, lazy loading stratejilerini ve gerçek dünya kullanım senaryolarını detaylı şekilde ele alacağız.

React.lazy ile Component Lazy Loading

React.lazy, bir component'i dinamik olarak import etmenizi sağlar. Bu sayede ilgili component sadece gerçekten ihtiyaç duyulduğunda yüklenir ve ana bundle boyutu küçülür. Özellikle route bazlı code splitting için ideal bir çözümdür.

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

// Lazy loaded route componentleri
const HomePage = lazy(() => import('./pages/HomePage'));
const ProductsPage = lazy(() => import('./pages/ProductsPage'));
const ProductDetail = lazy(() => import('./pages/ProductDetail'));
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
const UserProfile = lazy(() => import('./pages/UserProfile'));

// Named export için lazy loading
const Settings = lazy(() =>
  import('./pages/Settings').then((module) => ({
    default: module.Settings,
  }))
);

// Fallback component
function PageLoader() {
  return (
    <div className="flex items-center justify-center min-h-screen">
      <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" />
      <span className="ml-3 text-gray-600">Sayfa yükleniyor...</span>
    </div>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/products" element={<ProductsPage />} />
          <Route path="/products/:id" element={<ProductDetail />} />
          <Route path="/admin" element={<AdminDashboard />} />
          <Route path="/profile" element={<UserProfile />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}
İpucu: Lazy loading'i sadece route seviyesinde değil, ağır componentler (editörler, grafikler, haritalar) için de kullanabilirsiniz. Bu şekilde kullanıcı bir butona tıklayınca yüklenen modal veya panel componentleri oluşturabilirsiniz.

Suspense ile Veri Çekme (Data Fetching)

React 18+ ile Suspense, sadece lazy loading için değil, veri çekme operasyonları için de kullanılabilir. Bu özellik özellikle React Server Components ve TanStack Query ile birlikte güçlü bir deneyim sunar.

import { Suspense } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';

interface User {
  id: string;
  name: string;
  email: string;
  avatar: string;
}

function UserCard({ userId }: { userId: string }) {
  // useSuspenseQuery, veri gelene kadar Suspense boundary'yi tetikler
  const { data: user } = useSuspenseQuery<User>({
    queryKey: ['user', userId],
    queryFn: async () => {
      const res = await fetch(`/api/users/${userId}`);
      if (!res.ok) throw new Error('Kullanıcı bulunamadı');
      return res.json();
    },
  });

  return (
    <div className="flex items-center gap-3 p-4 border rounded-lg">
      <img src={user.avatar} alt={user.name} className="w-12 h-12 rounded-full" />
      <div>
        <h3 className="font-semibold">{user.name}</h3>
        <p className="text-gray-500">{user.email}</p>
      </div>
    </div>
  );
}

function UserCardSkeleton() {
  return (
    <div className="flex items-center gap-3 p-4 border rounded-lg animate-pulse">
      <div className="w-12 h-12 bg-gray-200 rounded-full" />
      <div>
        <div className="h-4 w-32 bg-gray-200 rounded mb-2" />
        <div className="h-3 w-48 bg-gray-200 rounded" />
      </div>
    </div>
  );
}

// Kullanım
export default function UserProfilePage({ userId }: { userId: string }) {
  return (
    <Suspense fallback={<UserCardSkeleton />}>
      <UserCard userId={userId} />
    </Suspense>
  );
}

Nested Suspense Boundaries

Birden fazla Suspense boundary kullanarak, sayfanın farklı bölümlerinin bağımsız olarak yüklenmesini sağlayabilirsiniz. Bu yaklaşım, kullanıcıya aşamalı bir yükleme deneyimi sunar ve algılanan performansı artırır.

import { Suspense, lazy } from 'react';

const HeavyChart = lazy(() => import('./components/HeavyChart'));
const CommentSection = lazy(() => import('./components/CommentSection'));
const RecommendedProducts = lazy(() => import('./components/RecommendedProducts'));

function ChartSkeleton() {
  return <div className="h-64 bg-gray-100 rounded-lg animate-pulse" />;
}

function CommentsSkeleton() {
  return (
    <div className="space-y-3">
      {[1, 2, 3].map((i) => (
        <div key={i} className="h-16 bg-gray-100 rounded animate-pulse" />
      ))}
    </div>
  );
}

function ProductsSkeleton() {
  return (
    <div className="grid grid-cols-4 gap-4">
      {[1, 2, 3, 4].map((i) => (
        <div key={i} className="h-48 bg-gray-100 rounded-lg animate-pulse" />
      ))}
    </div>
  );
}

export default function DashboardPage() {
  return (
    <main className="space-y-8">
      <h1>Dashboard</h1>

      {/* Grafik bağımsız olarak yüklenir */}
      <Suspense fallback={<ChartSkeleton />}>
        <HeavyChart />
      </Suspense>

      <div className="grid grid-cols-2 gap-8">
        {/* Yorumlar bağımsız olarak yüklenir */}
        <Suspense fallback={<CommentsSkeleton />}>
          <CommentSection />
        </Suspense>

        {/* Önerilen ürünler bağımsız olarak yüklenir */}
        <Suspense fallback={<ProductsSkeleton />}>
          <RecommendedProducts />
        </Suspense>
      </div>
    </main>
  );
}
Uyarı: Suspense fallback'ları mümkün olduğunca gerçekçi skeleton'lar olmalıdır. Basit bir "Yükleniyor..." yazısı yerine, yüklenecek içeriğin boyutlarına uygun placeholder'lar kullanmak layout shift'i önler ve kullanıcı deneyimini artırır.

Error Boundary ile Hata Yönetimi

Suspense ile birlikte Error Boundary kullanmak, lazy loading veya veri çekme sırasında oluşabilecek hataları zarif şekilde ele almanızı sağlar. Her Suspense boundary'nin yanında bir Error Boundary kullanmak iyi bir pratiktir.

import { Component, Suspense, lazy, ReactNode } from 'react';

interface ErrorBoundaryProps {
  fallback: ReactNode;
  children: ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
  error: Error | null;
}

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  state: ErrorBoundaryState = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
}

// Kullanım
function ErrorMessage({ message }: { message: string }) {
  return (
    <div className="bg-red-50 border border-red-200 text-red-700 p-4 rounded-lg">
      <p>Bir hata oluştu: {message}</p>
      <button onClick={() => window.location.reload()} className="mt-2 underline">
        Sayfayı Yenile
      </button>
    </div>
  );
}

const HeavyComponent = lazy(() => import('./HeavyComponent'));

export default function SafeSection() {
  return (
    <ErrorBoundary fallback={<ErrorMessage message="Component yüklenemedi" />}>
      <Suspense fallback={<div>Yükleniyor...</div>}>
        <HeavyComponent />
      </Suspense>
    </ErrorBoundary>
  );
}
Bilgi: React 18+ ile birlikte, Suspense component'i artık sadece lazy loading değil, aynı zamanda sunucu tarafı streaming render için de kullanılır. Next.js App Router'da loading.tsx dosyaları aslında otomatik Suspense boundary'leridir.

Preloading Stratejileri

Lazy loading performansını daha da artırmak için preloading tekniklerini kullanabilirsiniz. Kullanıcının bir linke hover ettiğinde veya sayfanın idle zamanında componentleri önceden yükleyerek gecikmeyi ortadan kaldırabilirsiniz. Bu teknik özellikle öngörülebilir kullanıcı davranışlarında çok etkilidir.

Sonuç

React Suspense ve Lazy Loading, modern React uygulamalarında performans optimizasyonunun temel taşlarıdır. Route bazlı code splitting ile başlayıp, ağır componentlerin lazy loading'i ve Suspense ile veri çekme gibi ileri tekniklerle uygulamanızın performansını kademeli olarak iyileştirebilirsiniz. Skeleton loader'lar ve Error Boundary'ler ile birlikte kullanıldığında, kullanıcıya kesintisiz ve güvenilir bir deneyim sunarsınız.

Kaynaklar

İlgili Yazılar