Kurumsal Dashboard'da Skeleton Loading ve UX Optimizasyonu

React'te skeleton loading nasıl uygulanır? CSS-only skeleton, React Suspense, optimistic updates ve error boundary ile dashboard UX optimizasyonu rehberi.

Neden Skeleton Loading?

Veri yüklenirken boş ekran veya dönen spinner göstermek hızlı hissetmez. Skeleton loading, içeriğin yerleşimini göstererek kullanıcıya “veri geliyor” mesajı verir — deneyim %40 daha hızlı algılanır.

CSS-Only Skeleton (Framework Bağımsız)

.skeleton {
  background: linear-gradient(
    90deg,
    var(--color-bg-muted) 25%,
    var(--color-border) 50%,
    var(--color-bg-muted) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.skeleton-text {
  height: 1em;
  margin-bottom: 0.5em;
}

.skeleton-card {
  height: 120px;
  border-radius: 12px;
}

.skeleton-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
}
<!-- Yükleniyor durumu -->
<div class="kpi-card">
  <div class="skeleton skeleton-text" style="width: 60%"></div>
  <div class="skeleton skeleton-text" style="width: 40%"></div>
</div>

React Skeleton Bileşeni

interface SkeletonProps {
  width?: string;
  height?: string;
  borderRadius?: string;
  count?: number;
}

export function Skeleton({ 
  width = '100%', 
  height = '1em', 
  borderRadius = '4px',
  count = 1 
}: SkeletonProps) {
  return (
    <>
      {Array.from({ length: count }).map((_, i) => (
        <div
          key={i}
          className="skeleton"
          style={{ width, height, borderRadius }}
        />
      ))}
    </>
  );
}

// KPI kartı skeleton'ı
export function KPICardSkeleton() {
  return (
    <div className="kpi-card">
      <Skeleton width="40%" height="0.875em" />
      <Skeleton width="60%" height="2em" />
      <Skeleton width="30%" height="0.75em" />
    </div>
  );
}

// Tablo skeleton'ı
export function TableSkeleton({ rows = 5 }: { rows?: number }) {
  return (
    <div className="table-skeleton">
      <Skeleton height="2.5em" borderRadius="8px" />
      {Array.from({ length: rows }).map((_, i) => (
        <Skeleton key={i} height="3em" />
      ))}
    </div>
  );
}

Skeleton vs Spinner — Ne Zaman Hangisi?

DurumSkeletonSpinner
İlk sayfa yüklemesi
Tablo veri yükleme
Buton tıklama (kaydet)
Modal açılışı
Infinite scroll
Dosya yükleme✅ (progress bar)

Kural: İçeriğin yerleşimi biliniyorsa → Skeleton. Bilinmiyorsa → Spinner.

React Suspense ile Entegrasyon

import { Suspense, lazy } from 'react';

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

function Dashboard() {
  return (
    <div className="dashboard">
      <Suspense fallback={<KPICardSkeleton />}>
        <DashboardStats />
      </Suspense>
      <Suspense fallback={<TableSkeleton rows={10} />}>
        <RecentOrders />
      </Suspense>
    </div>
  );
}

Optimistic Updates

Kullanıcı aksiyonunu anında UI’da gösterip, arka planda API’ye gönderin:

function TodoList() {
  const [todos, setTodos] = useState<Todo[]>([]);

  const addTodo = async (text: string) => {
    const optimisticTodo = { id: crypto.randomUUID(), text, done: false };
    
    // 1. Anında UI'da göster
    setTodos(prev => [...prev, optimisticTodo]);
    
    try {
      // 2. Arka planda API'ye gönder
      const saved = await api.createTodo(text);
      // 3. Gerçek ID ile güncelle
      setTodos(prev => prev.map(t => 
        t.id === optimisticTodo.id ? saved : t
      ));
    } catch {
      // 4. Hata olursa geri al
      setTodos(prev => prev.filter(t => t.id !== optimisticTodo.id));
    }
  };
}

Error Boundary

Hata durumunda tüm sayfayı çökertmek yerine ilgili bölümü gösterin:

import { Component, ReactNode } from 'react';

interface Props { children: ReactNode; fallback: ReactNode; }
interface State { hasError: boolean; }

class ErrorBoundary extends Component<Props, State> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

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

// Kullanım
<ErrorBoundary fallback={<p>Bu bölüm yüklenemedi.</p>}>
  <DashboardStats />
</ErrorBoundary>

İlgili Yazılar


Bu rehber React’in Suspense ve Error Boundary dokümantasyonuna dayanarak hazırlanmıştır.

📚 İlgili Yazılar