A React data grid can feel fast with a few thousand records but struggle when the dataset grows to hundreds of thousands or millions of rows.
The issue isn't necessarily the grid component itself. Rendering too many DOM elements, downloading large datasets, and performing sorting or filtering in the browser can all create performance bottlenecks.
To build a scalable React data grid, two techniques are especially important: buffered rendering and server-side data processing.

  1. Why large React data grids become slow
    A data grid displaying one million rows and 20 columns could potentially involve 20 million cells if every row is rendered.
    That creates several challenges:
    DOM overhead: Too many elements make rendering and scrolling expensive.
    Memory consumption: Downloading and storing millions of records increases browser memory usage.
    Network overhead: Large responses take longer to transfer.
    JavaScript processing: Sorting and filtering large datasets can block the main thread.
    These problems require different solutions. Virtualization reduces rendering work, while server-side processing reduces the amount of data handled by the browser.

  2. Use buffered rendering to reduce DOM elements
    Buffered rendering, also known as row virtualization, renders only the rows currently visible in the grid, along with a small buffer above and below the viewport.
    As users scroll, the grid reuses DOM elements to display new rows.
    This keeps the number of rendered elements relatively stable, even when the underlying dataset contains hundreds of thousands of records.
    In ReExt, the Ext JS grid provides buffered rendering by default.
    For example:
    import ReExt from '@sencha/reext';

function OrdersGrid({ orders }) { return ( <ReExt xtype="grid" style={{ height: 520 }} config={{ store: { data: orders }, columns: [ { text: 'Order ID', dataIndex: 'id', width: 110 }, { text: 'Customer', dataIndex: 'customer', flex: 1 }, { text: 'Region', dataIndex: 'region', width: 120 }, { text: 'Amount', dataIndex: 'amount', width: 130 } ] }} /> ); } 

This approach helps when the dataset can reasonably be loaded into the browser. However, virtualization does not eliminate the memory cost of storing every record locally.
For millions of records, server-side data loading becomes important.

  1. Move large datasets to the server
    Instead of downloading the entire dataset, the grid requests only the records it needs.
    The server handles:
    Sorting
    Filtering
    Pagination
    Data retrieval
    Total record counts
    The browser receives a smaller response and displays the returned rows.
    This architecture reduces unnecessary network transfers and client-side processing.

  2. Infinite scrolling with a buffered store
    Infinite scrolling allows users to browse records continuously. Additional data is fetched as they approach the end of the currently loaded rows.
    A ReExt buffered store can be configured like this:

store: { type: 'buffered', pageSize: 200, leadingBufferZone: 600, trailingBufferZone: 200, purgePageCount: 5, proxy: { type: 'ajax', url: '/api/orders', reader: { type: 'json', rootProperty: 'data', totalProperty: 'total' } }, autoLoad: true } 

Here:
pageSize controls how many records are requested per page.
leadingBufferZone prefetches records ahead of the current scroll position.
trailingBufferZone retains records behind the viewport.
purgePageCount controls how many pages remain cached before older pages are discarded.
This approach works well for applications where users need to scan large datasets without navigating between numbered pages.

  1. When pagination is a better option Infinite scrolling isn't ideal for every application. Pagination is often more appropriate when users need to: Find specific records. Navigate to a known page. Review reports. Work with predictable API requests. A paged store can use remote sorting and filtering:
store: { pageSize: 100, remoteSort: true, remoteFilter: true, proxy: { type: 'ajax', url: '/api/orders', reader: { type: 'json', rootProperty: 'data', totalProperty: 'total' } }, autoLoad: true }, bbar: { xtype: 'pagingtoolbar', displayInfo: true } 

The choice depends on the user experience and the application's data-access requirements.

  1. Infinite scrolling vs. pagination Consideration Infinite scrolling Pagination User experience Continuous browsing Numbered pages Best suited for Monitoring and data exploration Reports and record lookups Data requests Smaller requests during scrolling Requests when changing pages Navigation Scroll through records Jump to a specific page Implementation Requires buffered data loading Usually simpler API workflow

Both approaches can work with server-side sorting and filtering.

  1. Don't forget database performance Moving data processing to the server doesn't automatically make an application fast. The database and API must also be designed for large datasets. Consider these practices: Add indexes to frequently filtered and sorted columns. Use parameterized queries. Allowlist sortable and filterable database columns. Limit the maximum number of records returned per request. Avoid expensive calculations on every request. Consider cursor-based pagination when deep offset pagination becomes slow. For example, a stable sort order is important when retrieving successive pages. Without it, records can appear more than once or be skipped when data changes.

8.** Important considerations at millions of rows**
Some grid features need additional planning when the complete dataset stays on the server.
Totals and summaries: Calculate full-dataset totals in the database rather than using only the records currently loaded in the browser.
Export: Generate large exports on the server using the same filters and sorting parameters as the grid.
Select all: Represent selection as all records matching the current filters rather than downloading millions of record IDs.
Grouping: Coordinate grouping and sorting on the server so groups remain consistent across pages.
Performance monitoring: Measure request latency, memory usage, first-row load time, and scrolling behavior with realistic data.

  1. How to test React data grid performance Don't rely only on vendor benchmarks or small sample datasets. Test with the actual number of rows and columns your users will work with. Useful metrics include: Time to first rendered rows. Scrolling smoothness. API response time. Browser memory consumption. Sorting and filtering interaction delay. Use browser developer tools to record performance and memory during realistic user interactions.

Conclusion
Handling millions of rows in a React data grid requires more than optimizing React rendering.
Buffered rendering keeps the DOM manageable, while server-side data loading prevents the browser from downloading and processing the entire dataset. Together, these techniques provide a more scalable architecture for enterprise applications.
For React teams evaluating an integrated solution, ReExt brings the Ext JS grid into React and supports buffered rendering, buffered stores, and paged data configurations.
The best approach is to test both rendering and data-loading strategies with production-sized datasets before choosing an architecture.
Further reading: How to Handle Millions of Rows in a React Data Grid