Ana içeriğe geç
22 Kasım 2025 Vue 3PerformanceWeb WorkersVirtual Scroll

Optimizing a Slow Data Dashboard (10k+ Rows)

10.000+ satırlık veri içeren bir tabloda filtreleme yapıldığında tarayıcının donması ve scroll performansının düşmesi.

Sorunun Analizi

Lojistik projemizde, 10.000+ satırlık sevkiyat verisini tek bir tabloda göstermemiz gerekiyordu. İlk başta standart bir v-for döngüsü ile tüm veriyi render etmeyi denedik. Ancak bu yaklaşım, DOM'da 10.000'den fazla tr elemanı oluşturduğu için tarayıcının bellek kullanımını artırdı ve render süresini uzattı.

Kullanıcı arama kutusuna her harf yazdığında (filtering), Vue tüm listeyi yeniden hesaplayıp DOM'u güncelliyordu. Bu işlem JavaScript'in Main Thread'ini 2-3 saniye boyunca blokluyor, arayüzün donmasına (freeze) neden oluyordu.

Çözüm Yaklaşımı

Sorunu iki aşamada çözdük: Render yükünü azaltmak için Virtual Scrolling, hesaplama yükünü ana thread'den almak için Web Workers.

1. Virtual Scrolling (Sanal Kaydırma)

DOM'daki eleman sayısını azaltmak için Virtual Scrolling tekniğini kullandık. Bu teknik, kullanıcının o an ekranda gördüğü (viewport) satırları (örneğin 20 adet) render eder. Kullanıcı aşağı kaydırdıkça, üstteki elemanlar silinir ve alttan yenileri eklenir. Böylece DOM boyutu her zaman sabit kalır.

2. Web Workers (Arka Plan İşlemleri)

Virtual Scroll render performansını çözdü ama filtreleme mantığı (10.000 satır üzerinde string search) hala Main Thread'de çalışıyordu. Bu hesaplamayı ana thread'den ayırmak için Web Worker kullandık.

Kod Uygulaması

        // 1. worker.ts (Ağır işlem burada yapılır)
self.onmessage = (e) => {
  const { data, filterText } = e.data;
  // Ağır filtreleme işlemi
  const filtered = data.filter(item => 
    item.name.toLowerCase().includes(filterText.toLowerCase()) ||
    item.id.includes(filterText)
  );
  // Sonucu geri gönder
  self.postMessage(filtered);
};

// 2. DashboardTable.vue
import { useVirtualList } from '@vueuse/core';
import { ref, watch } from 'vue';

const allData = ref([]); // 10k data
const filteredData = ref([]);
const filterText = ref('');

// Worker'ı başlat
const worker = new Worker(new URL('./worker.ts', globalThis._importMeta_.url));

worker.onmessage = (e) => {
  filteredData.value = e.data; // Worker'dan gelen filtrelenmiş veri
};

// Filtre değişince Worker'a iş gönder
watch(filterText, (newVal) => {
  worker.postMessage({ data: allData.value, filterText: newVal });
});

// Virtual List ile sadece görüneni render et
const { list, containerProps, wrapperProps } = useVirtualList(
  filteredData,
  { itemHeight: 50 }
);
      

İlgili Kaynaklar