A high-performance React application demonstrating how to efficiently render and interact with millions of rows of data using React virtualization techniques.
This project showcases the power of React virtualization by rendering a million rows of planetary scan data, prime numbers, and mock cyber threat data without performance degradation. It uses react-window to implement windowing/virtualization, ensuring smooth scrolling and minimal memory usage even with massive datasets.
React virtualization (also known as "windowing") is a technique for efficiently rendering large lists by only rendering the items that are currently visible in the viewport, plus a small buffer. Instead of rendering all million rows at once (which would crash the browser), virtualization creates a "window" that shows only what's needed.
┌─────────────────────────────────────┐
│ Total Dataset │
│ (1,000,000 rows) │
│ │
│ ┌─────────────────────────────┐ │
│ │ Viewport (Visible Area) │ │ ← Only these ~11 rows
│ │ • Row 10,001 │ │ are visible (550px / 50px)
│ │ • Row 10,002 │ │ + 10 buffer rows (5 above, 5 below)
│ │ • Row 10,003 │ │ = ~21 DOM nodes total
│ │ • Row 10,004 │ │
│ │ • Row 10,005 │ │
│ └─────────────────────────────┘ │
│ │
│ [Data loaded in 1000-row chunks │
│ as user scrolls] │
│ │
└─────────────────────────────────────┘
// Without virtualization - DON'T DO THIS!
const TraditionalList = ({ data }) => {
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.content}</div>
))}
</div>
);
};
// Result: 1M DOM nodes = Browser crash/freeze// With react-window virtualization (our implementation)
import { FixedSizeList } from "react-window";
const VirtualizedList = ({ data }) => {
const Row = ({ index, style }) => (
<div style={style}>{data[index].content}</div>
);
return (
<FixedSizeList
height={550} // Viewport height
itemCount={data.length} // Currently loaded items
itemSize={50} // Row height
width="100%"
overscanCount={5} // Buffer rows
>
{Row}
</FixedSizeList>
);
};
// Result: ~21 DOM nodes = Smooth performance┌───────────────────────────────────────────────────┐
│ Performance Metrics │
├────────────────┬───────────────┬──────────────────┤
│ Metric │ Traditional │ Virtualized │
├────────────────┼───────────────┼──────────────────┤
│ DOM Nodes │ 1,000,000 │ ~21 │
│ Memory Usage │ ~4-6 GB │ ~50-100 MB │
│ Initial Load │ 30-60 sec │ ~100 ms │
│ Chunk Size │ All at once │ 1000 rows │
│ Scroll FPS │ 1-5 │ 60 │
│ Interaction │ Unusable │ Instant │
└────────────────┴───────────────┴──────────────────┘
Only renders visible items plus a small overscan (buffer) for smooth scrolling:
// In our implementation
const ROW_HEIGHT = 50; // Fixed row height in pixels
const VIEWPORT_HEIGHT = 550; // Visible area height
const OVERSCAN_COUNT = 5; // Buffer rows above and below viewportCreates the illusion of scrolling through all data:
- Virtual Height: Container has the full height (1M rows × row height)
- Transform: Visible items are positioned using CSS transforms
- Scroll Events: Calculate which items should be visible based on scroll position
Data is fetched in chunks as needed:
// Our chunking strategy
const CHUNK_SIZE = 1000; // Fetch 1000 rows at a time
// Load more when scrolled past 80% of loaded content
const scrollPercentage = scrollOffset / (itemCount * ROW_HEIGHT - VIEWPORT_HEIGHT);
if (scrollPercentage > 0.8 && hasMore && !isLoadingMore) {
loadNextChunk();
}Without Virtualization:
┌──────────────────────────────────────┐
│ Memory: ████████████████████████████ │ 4GB+
│ Every row = DOM node + JS object │
└──────────────────────────────────────┘
With Virtualization:
┌──────────────────────────────────────┐
│ Memory: ██░░░░░░░░░░░░░░░░░░░░░░░░░ │ 100MB
│ Only visible rows in DOM │
└──────────────────────────────────────┘
- Request Management: Singleton pattern prevents duplicate API calls
- Memoization: Row components are memoized to prevent unnecessary re-renders
- Scroll-based Loading: Loads next chunk when scrolled past 80% of content
- Response Caching: 5-minute TTL cache for previously fetched data
- Loading States: Visual feedback during chunk loading with spinners
- Next.js 15 - React framework with App Router
- React Window - Virtualization library for efficient rendering
- TypeScript - Type safety and better developer experience
- Prisma - Database ORM with PostgreSQL
- Tailwind CSS - Utility-first styling
- Supabase - Database hosting and management
- Node.js 18+
- npm/yarn/pnpm
- PostgreSQL (or SQLite for development)
- Clone the repository
git clone https://github.com/akdevv/million-rows.git
cd million-rows- Install dependencies
bun install- Set up environment variables
cp .env.example .env.local
# Edit .env.local with your database connection- Set up the database
bunx prisma migrate dev
bunx prisma generate- Generate sample data
bun scripts/generate-prime-numbers.js
bun scripts/generate-cyber-threats.js
bun scripts/generate-planetary-scans.js
# Then upload the data to Supabase- Run the development server
bun run dev- Open in browser
http://localhost:3000
- Select a dataset from the dropdown
- Watch as the table loads and renders efficiently
- Scroll smoothly through millions of rows
- Notice the performance metrics in the browser DevTools
MIT
Contributions are welcome! Please feel free to submit a Pull Request.