Understand the rendering model
Without virtualization, the browser may create DOM for every visible and offscreen row and cell. Style calculation, layout, paint, memory, and React reconciliation grow with that rendered surface. Row virtualization renders a moving vertical window; horizontal virtualization limits columns in wide grids; cell-content virtualization can defer expensive inner content. These techniques preserve a large logical dataset while keeping the mounted interface closer to the viewport.
Configure Ace Grid virtualization deliberately
Ace Grid exposes enableVirtualization, enableHorizontalVirtualization, and enableCellContentVirtualization through the virtual prop group. Start with row virtualization for tall datasets, add horizontal virtualization for many wide columns, and use content virtualization only when cell bodies are measurably expensive. Give the grid a bounded height and stable dimensions. Change one option at a time so performance improvements and interaction regressions can be attributed correctly.
Use stable row identity
Virtualized DOM is reused as the viewport moves, so record identity cannot depend on visual position. Every row needs a stable ID that survives sorting, filtering, loading, and updates. Test editing and selection while rapidly scrolling, then return to the same record and confirm state is attached to the row rather than the recycled element. Unstable IDs can produce edits, focus, or selection on the wrong business record.
Plan row height and overscan
Predictable row height makes scroll offsets and viewport calculations reliable. Variable heights require measurement and can cause jumps when content changes. Overscan renders a buffer beyond the visible area to avoid blank edges during fast movement, but excessive overscan recreates much of the DOM cost virtualization should remove. Test trackpad, mouse wheel, scrollbar drag, Page Down, keyboard navigation, and programmatic scrolling on target hardware.
Keep focus correct across recycled cells
A virtualized cell can leave the DOM while it remains logically selected or active. Define what happens when users scroll an edited cell away, move focus beyond the rendered window, or return to a selected row. Test pinned columns, pinned rows, custom controls, validation messages, and context menus. Performance is not acceptable if users lose keyboard position, commit an edit unexpectedly, or cannot return to the active record.
Separate virtualization from infinite loading
Virtualization controls rendering; infinite loading controls when additional rows are requested. Ace Grid scroll props support host-driven loading with batch size, threshold, direction, offset, page, or cursor strategies. The grid requests data, but the application updates the row source. Define duplicate prevention, cursor advancement, retry, end-of-data, and selection behavior across loaded windows. A fast viewport cannot compensate for an unreliable loading contract.
Know when to use a server row model
Use client virtualization when the browser can store and process the working set. Use infinite loading for sequential discovery where appending pages is sufficient. Evaluate Enterprise server row model when the server must own global filtering, sorting, grouping, pivoting, or paging. Server-backed grids require stable ordering, request cancellation, cache policy, total-row semantics, and authorization. These are data architecture decisions, not virtualization settings.
Benchmark a representative production grid
Record browser, device, row count, column count, row height, renderer complexity, dataset location, and enabled features. Measure time to usable interaction, fast-scroll blanking, edit-start delay, filter response, memory growth, and long tasks. Repeat the same scripted tasks before and after each change. Avoid publishing a universal row limit because performance depends on the complete rendering and data-processing workload.
Recognize common failure modes
Watch for changing row heights, unstable IDs, expensive renderers, synchronous formatting, oversized overscan, controlled state that updates every scroll event, and remote requests without cancellation. Also test loading placeholders, empty results, errors, stale responses, and rapid filter changes. Fix the dominant measured cost first. Enabling every virtualization option cannot repair slow queries, unnecessary React work, or custom cells that perform expensive computation during render.
Test pinned and grouped surfaces
Pinned rows, pinned columns, group headers, detail panels, and totals can follow different rendering paths from the central viewport. Verify alignment, focus movement, resizing, and scroll synchronization when these features are combined with virtualization. Test a wide grid with both vertical and horizontal movement because issues may appear only when the active cell crosses a pinned boundary or a virtualized column is remounted.
Optimize data transformations separately
Client sorting, filtering, grouping, aggregation, and formatting may dominate performance even when DOM rendering is bounded. Profile these operations independently. Avoid rebuilding columns or large row objects on every render, and debounce user-driven server queries where appropriate. If client transformations exceed the interaction budget, move the operation to a worker or server rather than increasing virtualization settings.
Define a performance budget
Set measurable thresholds for the target devices: time to first usable interaction, maximum edit-start delay, acceptable filter response, scroll blanking, long-task duration, and memory growth. Budgets make regressions visible in CI or release testing. Use representative data generators and custom renderers so tests remain repeatable. A performance claim without hardware, workload, and measurements is not useful evidence.
Live Ace Grid example
Virtualized viewport preview
Visible row windows, load states, render health, and scroll actions for large-grid evaluation.
Virtualized Ace Grid example
import { Grid } from "@ace-grid/core";
export function VirtualizedOrdersGrid({ rows, columns, loadMoreRows }) {
return (
<Grid
data={{ rows, columns }}
columns={{ columnWidths: {}, fillWidth: true }}
layout={{ width: 1200, height: 600 }}
virtual={{
enableVirtualization: true,
enableHorizontalVirtualization: true,
enableCellContentVirtualization: true,
}}
scroll={{
enableInfiniteScroll: true,
infiniteScrollBatchSize: 100,
loadMoreRows,
}}
/>
);
}