Vue cookbook · Advanced

Render large lists efficiently

A table with thousands of rows makes typing and scrolling sluggish.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Store large, replace-only data in shallowRef (no deep proxies), render only visible rows with a virtual list, and use v-memo to skip re-rendering rows whose inputs didn't change.

Solution

<script setup lang="ts">
import { computed, ref, shallowRef } from "vue";

type Row = { id: number; name: string; price: number };
const rows = shallowRef<Row[]>([]);                     // replaced as a whole, never mutated deeply
const selectedId = ref<number | null>(null);
const filter = ref("");

async function load() {
  rows.value = await (await fetch("/api/rows")).json();
}
load();

const visible = computed(() => {
  const f = filter.value.toLowerCase();
  return f ? rows.value.filter((r) => r.name.toLowerCase().includes(f)) : rows.value;
});

function rename(id: number, name: string) {
  rows.value = rows.value.map((r) => (r.id === id ? { ...r, name } : r));   // new array triggers update
}
defineExpose({ rename });
</script>

<template>
  <input v-model="filter" placeholder="Filter" />
  <ul>
    <li
      v-for="row in visible.slice(0, 200)"
      :key="row.id"
      v-memo="[row.name, row.price, row.id === selectedId]"
      :class="{ selected: row.id === selectedId }"
      @click="selectedId = row.id"
    >
      {{ row.name }}: {{ row.price }}
    </li>
  </ul>
</template>

Pitfalls

  • Making large API payloads deeply reactive with ref/reactive for no benefit.
  • Mutating items inside a shallowRef and expecting the view to update.
  • v-memo dependency lists that miss a value the template uses, leaving stale rows.

Variations

  • Virtualize with vue-virtual-scroller or TanStack Virtual to render only visible rows.
  • Move filtering of huge datasets to the server or a Web Worker.

Further reading: Performance

Reference: https://vuejs.org/guide/best-practices/performance.html

More Vue recipes

Was this page helpful?