Vue cookbook · Advanced
Render large lists efficiently
A table with thousands of rows makes typing and scrolling sluggish.
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/reactivefor no benefit. - Mutating items inside a
shallowRefand expecting the view to update. v-memodependency lists that miss a value the template uses, leaving stale rows.
Variations
- Virtualize with
vue-virtual-scrolleror 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
- Access child component methods with template refs and defineExpose
- Add SEO metadata and structured data in Nuxt
- Build an accessible modal with Teleport
- Build custom inputs with defineModel and modifiers
- Catch component errors with an error boundary
- Debounce user input with a custom ref
- Fetch data in a composable and cancel stale requests
- Fetch, refresh and share data with Nuxt useFetch