A hands-on frontend engineering lab for studying and demonstrating real-world performance patterns in React applications.
The project focuses on understanding performance bottlenecks, measuring them with browser profiling tools, and applying targeted optimizations rather than premature optimization.
This project explores how frontend performance is affected by:
- Rendering large datasets
- DOM size and rendering cost
- Request frequency and debouncing
- Server-state caching
- Request cancellation
- List virtualization
- Responsive virtualization
- Dynamic row measurement
- Image and asset loading
- React rendering behavior
- Browser rendering and layout
The goal is not only to make the application faster, but to understand why a particular optimization works and what trade-offs it introduces.
A baseline implementation using React state and effects.
Explores:
useStateuseEffect- Debouncing
AbortController- Request lifecycle
- Loading and error states
The same search experience implemented with TanStack Query.
Explores:
- Server-state management
- Query keys
- Caching
- Stale data
- Request cancellation
- Loading and error states
A large dataset containing 10,000 generated products.
The case compares:
- Normal rendering
- Virtualized rendering
It demonstrates why rendering thousands of DOM nodes can become expensive and how virtualization reduces the amount of DOM that needs to be rendered.
The virtualization implementation also supports:
- Responsive column counts
- Container-based responsive behavior
- Dynamic row measurement
- Variable row heights
- Overscan
src/
├── cases/
│ ├── native-search/
│ │ └── NativeSearch.tsx
│ ├── tanstack-search/
│ │ └── TanStackSearch.tsx
│ └── virtualization/
│ └── Virtualization.tsx
├── components/
│ ├── SearchInput.tsx
│ ├── ProductList.tsx
│ ├── ProductCard.tsx
│ └── VirtualizedProductList.tsx
├── hooks/
│ ├── useDebounce.ts
│ └── useResponsiveColumns.ts
├── lib/
│ ├── api.ts
│ └── generateProducts.ts
├── types/
│ └── product.ts
├── App.tsx
└── main.tsx
The project is intentionally organized by performance case and shared UI primitives.
Each case is isolated so different approaches can be compared without mixing their implementation details.
- React — UI library
- TypeScript — Type safety
- Vite — Development and build tooling
- Tailwind CSS — Styling
- React Router — Client-side routing
- TanStack Query — Server-state management and caching
- TanStack Virtual — List virtualization
The lab follows a simple principle:
Measure first, optimize second.
Instead of automatically adding useMemo, useCallback, or other optimizations, each performance problem should first be identified through profiling and then addressed at the appropriate layer.
Examples:
| Problem | Potential solution |
|---|---|
| Too many requests | Debouncing |
| Stale concurrent requests | AbortController / request cancellation |
| Repeated server-state fetching | TanStack Query |
| Too many DOM nodes | Virtualization |
| Expensive calculations | Algorithm optimization / memoization |
| Large JavaScript payload | Code splitting / lazy loading |
| Large images | Responsive images / modern formats |
| Font overhead | Reduce weights / subsets |
| Slow initial rendering | Rendering and asset optimization |
The search cases demonstrate two different approaches to managing asynchronous data.
User input
↓
Debounce
↓
Fetch request
↓
Abort stale request if necessary
↓
Loading / Error / Data
User input
↓
Debounce
↓
Query key
↓
TanStack Query
├── Cache
├── Request lifecycle
└── Cancellation
↓
Loading / Error / Data
The comparison helps demonstrate the difference between manually managing server state and using a dedicated server-state library.
The virtualization case uses a dataset of 10,000 products.
Normal rendering creates a DOM representation for all products:
10,000 products
↓
10,000 ProductCard components
↓
Large DOM tree
↓
Higher rendering / layout / painting cost
Virtualized rendering keeps only the rows around the visible viewport:
10,000 products
↓
Virtualizer
↓
Only visible + overscan rows
↓
Much smaller DOM tree
The virtualized grid determines its column count from the actual container width using ResizeObserver.
Container width
↓
ResizeObserver
↓
useResponsiveColumns
↓
columnCount
├── 2 columns
├── 3 columns
└── 4 columns
The same columnCount is used for both:
- CSS grid layout
- Virtualized row calculation
This keeps the visual layout and virtualization model consistent.
The virtualizer uses an initial estimate:
estimateSize: () => 180This is only an initial prediction.
After the row is rendered, TanStack Virtual can measure the actual DOM element through:
ref={rowVirtualizer.measureElement}The flow is:
estimateSize
↓
Initial virtual layout
↓
Row rendered
↓
measureElement
↓
Actual DOM height
↓
Virtualizer updates measurements
↓
getTotalSize()
This allows the virtualized list to work with responsive and variable row heights without hardcoding a fixed row height.
Browser DevTools is used to investigate:
- Network waterfalls
- JavaScript execution
- Rendering
- Layout
- Painting
- Garbage collection
- DOM operations
- React rendering behavior
The investigation process follows:
Perceived slowness
↓
Identify the suspected bottleneck
↓
Measure with profiling tools
↓
Apply a targeted optimization
↓
Re-measure
↓
Compare the result
The goal is to understand whether the bottleneck comes from:
- JavaScript execution
- React rendering
- DOM creation
- Layout
- Style recalculation
- Painting
- Network requests
- Asset loading
- Memory pressure
Install dependencies:
npm installStart the development server:
npm startOr:
npm run devCreate a production build:
npm run buildPreview the production build locally:
npm run preview| Route | Description |
|---|---|
/ |
Project overview |
/native-search |
Native React search implementation |
/tanstack-search |
TanStack Query search implementation |
/virtualization |
Normal vs virtualized rendering comparison |
The current lab covers:
- React search with debouncing
- Request cancellation with
AbortController - TanStack Query server-state management
- Query caching
- Large dataset generation
- Normal list rendering
- List virtualization
- Responsive virtualized grids
- Dynamic row measurement
- Browser performance profiling
Future experiments will explore:
- Scroll performance profiling
- React rendering optimization
- Image optimization
- Font optimization
- Code splitting
- Lazy loading
- Next.js performance patterns
next/imagenext/font- Server Components vs Client Components
- Caching and revalidation
- Production performance analysis
This repository is intentionally built as an evolving engineering lab rather than a collection of isolated snippets.
Each experiment follows:
Problem
↓
Baseline implementation
↓
Measure
↓
Analyze bottleneck
↓
Apply targeted solution
↓
Measure again
↓
Document trade-offs
The objective is to develop performance intuition and engineering judgment, not simply memorize optimization techniques.
For educational and portfolio purposes.