---
name: lazy-loading
description: Use when loading all data upfront. Use when initial page load is slow. Use when fetching data that might not be needed.
---
# Lazy Loading
## Overview
**Load data when needed, not before. Don't fetch what you might not use.**
Upfront loading wastes bandwidth, memory, and time. Load on demand for better performance and user experience.
## When to Use
- Dashboard loads all data at once
- Page fetches data for hidden tabs
- Loading "just in case" data
- Initial load is slow
## The Iron Rule
```
NEVER load data until it's actually needed.
```
**No exceptions:**
- Not for "we might need it"
- Not for "parallel is faster"
- Not for "simpler to load everything"
- Not for "it's not that much data"
## Detection: Eager Overload Smell
If you load everything upfront, STOP:
```typescript
// ❌ VIOLATION: Load everything upfront
async function loadDashboard(userId: string) {
const [
profile,
preferences,
notifications, // User might not check
recentActivity, // Collapsed by default
analytics, // Expensive, rarely viewed
recommendations, // Below the fold
fullHistory // Paginated anyway
] = await Promise.all([
fetchProfile(userId),
fetchPreferences(userId),
fetchNotifications(userId),
fetchRecentActivity(userId),
fetchAnalytics(userId), // Takes 2 seconds!
fetchRecommendations(userId),
fetchFullHistory(userId) // 10MB of data!
]);
return { profile, preferences, notifications, ... };
}
```
Problems:
- Slowest fetch blocks everything
- Wastes resources on unused data
- Poor perceived performance
## The Correct Pattern: Load On Demand
```typescript
// ✅ CORRECT: Load critical data first, rest on demand
// Initial load - only what's immediately visible
async function loadDashboard(userId: string) {
const [profile, preferences] = await Promise.all([
fetchProfile(userId),
fetchPreferences(userId)
]);
return { profile, preferences };
}
// React component with lazy loading
function Dashboard({ userId }) {
// Critical data loaded immediately
const { profile, preferences } = useInitialData(userId);
// Notifications: load when header mounts
const notifications = useLazyQuery(
() => fetchNotifications(userId),
{ loadOn: 'mount' }
);
// Analytics: load when tab is selected
const [analyticsTab, setAnalyticsTab] = useState(false);
const analytics = useLazyQuery(
() => fetchAnalytics(userId),
{ loadOn: analyticsTab }
);
// History: load when scrolled into view
const historyRef = useRef();
const history = useLazyQuery(
() => fetchHistory(userId),
{ loadOn: useIntersectionObserver(historyRef) }
);
return (
;
}
```
### 3. Load on Route
```typescript
// Next.js / React Router
const AnalyticsPage = lazy(() => import('./AnalyticsPage'));
}>
} />
```
### 4. Pagination / Infinite Scroll
```typescript
function OrderList() {
const [page, setPage] = useState(1);
const { data, hasMore } = useOrders({ page, limit: 20 });
return (
<>
{data.map(order => )}
{hasMore && }
>
);
}
```
## Pressure Resistance Protocol
### 1. "We Might Need It"
**Pressure:** "Load it now in case user needs it"
**Response:** "Might" means probably won't. Load when they actually need it.
**Action:** Load on demand, not on speculation.
### 2. "Parallel Is Faster"
**Pressure:** "Loading everything in parallel is faster than sequential"
**Response:** Parallel loading of unneeded data is slower than not loading it.
**Action:** Parallelize what you need. Lazy load what you might need.
### 3. "Simpler to Load Everything"
**Pressure:** "One fetch function is simpler"
**Response:** Simple code that's slow and wasteful isn't simple.
**Action:** Structured lazy loading is maintainable and performant.
## Red Flags - STOP and Reconsider
- `Promise.all` with 5+ fetches on page load
- Fetching data for collapsed/hidden sections
- Loading full lists instead of paginating
- Slow initial page loads
- "Loading..." takes more than 1-2 seconds
**All of these mean: Implement lazy loading.**
## Quick Reference
| Eager (Usually Bad) | Lazy (Usually Good) |
|---------------------|---------------------|
| Load all on mount | Load visible content first |
| Fetch hidden tab data | Fetch when tab selected |
| Full list at once | Paginate / infinite scroll |
| Below-fold content | Intersection observer |
## Common Rationalizations (All Invalid)
| Excuse | Reality |
|--------|---------|
| "Might need it" | Load when you do need it. |
| "Parallel is faster" | Not loading is fastest. |
| "Simpler" | Slow isn't simple. |
| "It's not much data" | It adds up. Bandwidth costs. |
| "Better UX to have it ready" | Slow load is worse UX. |
## The Bottom Line
**Load what's visible. Defer the rest. Paginate large lists.**
Initial load = critical data only. Everything else loads on interaction, scroll, or navigation. Users shouldn't wait for data they won't see.