NuGrid uses virtualization to efficiently render large datasets by only rendering the visible rows.
Rendering 1,000 rows with virtualization. Only visible rows are rendered in the DOM.
<script setup lang="ts">
import type { NuGridColumn } from '#nu-grid/types'
interface Row {
id: number
name: string
value: number
category: string
}
// Generate 1000 rows
const data = ref<Row[]>(
Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
name: `Item ${i + 1}`,
value: Math.floor(Math.random() * 10000),
category: ['A', 'B', 'C', 'D'][i % 4]!,
})),
)
const columns: NuGridColumn<Row>[] = [
{ accessorKey: 'id', header: 'ID', size: 80 },
{ accessorKey: 'name', header: 'Name', size: 150 },
{ accessorKey: 'value', header: 'Value', size: 100 },
{ accessorKey: 'category', header: 'Category', size: 100 },
]
</script>
<template>
<div class="w-full">
<p class="mb-3 text-sm text-muted">
Rendering 1,000 rows with virtualization. Only visible rows are rendered in the DOM.
</p>
<div class="h-80">
<NuGrid
:data="data"
:columns="columns"
virtualization
:ui="{
base: 'w-full border-separate border-spacing-0',
thead: '[&>tr]:bg-elevated/50',
th: 'py-2 border-y border-default first:border-l last:border-r first:rounded-l-lg last:rounded-r-lg',
td: 'border-b border-default',
}"
/>
</div>
</div>
</template>
Enable virtualization with the virtualization prop:
<template>
<NuGrid
:data="data"
:columns="columns"
virtualization
/>
</template>
| Dataset Size | Recommendation |
|---|---|
| < 100 rows | Not needed |
| 100-500 rows | Optional |
| 500-1000 rows | Recommended |
| > 1000 rows | Strongly recommended |
Virtualization renders only the rows visible in the viewport plus a small buffer. As the user scrolls:
This keeps the DOM size constant regardless of data size.
For virtualization to work properly, the grid needs a defined height:
<template>
<!-- Fixed height -->
<div class="h-[600px]">
<NuGrid :data="data" :columns="columns" virtualization />
</div>
<!-- Flex container -->
<div class="flex h-screen flex-col">
<header>Header</header>
<div class="flex-1 min-h-0">
<NuGrid :data="data" :columns="columns" virtualization />
</div>
</div>
</template>
NuGrid calculates row heights automatically, but you can optimize by providing estimates:
<template>
<NuGrid
:data="data"
:columns="columns"
virtualization
:estimated-row-height="40"
/>
</template>
Virtualization handles variable row heights automatically:
const columns: NuGridColumn<Item>[] = [
{
accessorKey: 'description',
header: 'Description',
cell: ({ row }) => {
// Multi-line content is handled correctly
return h('div', { class: 'whitespace-pre-wrap' }, row.original.description)
},
},
]
NuGrid maintains scroll position during:
Keep cell renderers simple:
// Good - simple render
{
cell: ({ row }) => row.original.name
}
// Avoid - complex computation in render
{
cell: ({ row }) => {
const result = expensiveComputation(row.original)
return formatResult(result)
}
}
Move calculations outside the render:
<script setup lang="ts">
const processedData = computed(() =>
data.value.map(item => ({
...item,
computedField: expensiveComputation(item),
}))
)
</script>
<template>
<NuGrid
:data="processedData"
:columns="columns"
virtualization
/>
</template>
// Avoid - creates many reactive dependencies
{
cell: ({ row }) => someRef.value + row.original.value
}
// Better - use column-level computed
{
cell: ({ row, table }) => {
const context = table.options.meta
return context.multiplier * row.original.value
}
}
Hide columns that aren't needed:
<script setup lang="ts">
const columnVisibility = ref({
internalId: false,
debugInfo: false,
})
</script>
<template>
<NuGrid
v-model:column-visibility="columnVisibility"
:data="data"
:columns="columns"
virtualization
/>
</template>
You typically don't need virtualization with pagination:
<template>
<!-- Choose one or the other -->
<!-- Pagination for moderate datasets -->
<NuGrid :data="data" :columns="columns" :paging="{ pageSize: 50 }" />
<!-- Virtualization for large datasets without pagination -->
<NuGrid :data="data" :columns="columns" virtualization />
</template>
Virtualization works with grouping:
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
virtualization
/>
</template>
Selection state is maintained during virtualization:
<template>
<NuGrid
v-model:row-selection="rowSelection"
:data="data"
:columns="columns"
virtualization
selection="multi"
/>
</template>
Handle scroll events:
<script setup lang="ts">
function onScroll(event) {
console.log('Scroll position:', event.scrollTop)
}
</script>
<template>
<NuGrid
:data="data"
:columns="columns"
virtualization
@scroll="onScroll"
/>
</template>
NuGrid implements smooth scrolling behavior:
<template>
<NuGrid
:data="data"
:columns="columns"
virtualization
:scroll-options="{
behavior: 'smooth'
}"
/>
</template>
Typical performance with virtualization enabled:
| Rows | Initial Render | Scroll Performance |
|---|---|---|
| 1,000 | ~50ms | 60fps |
| 10,000 | ~60ms | 60fps |
| 100,000 | ~80ms | 60fps |
Performance depends on:
Ensure the container has a defined height:
<!-- Won't work - no height constraint -->
<NuGrid :data="data" :columns="columns" virtualization />
<!-- Will work -->
<div class="h-[500px]">
<NuGrid :data="data" :columns="columns" virtualization />
</div>
If scroll position jumps, try providing an estimated row height:
<template>
<NuGrid
:data="data"
:columns="columns"
virtualization
:estimated-row-height="44"
/>
</template>
Check that data is reactive:
<script setup lang="ts">
// Must be reactive
const data = ref([])
// Load data
data.value = await fetchData()
</script>