---
name: react-fetch-cache-patterns
description: Use this skill whenever building, reviewing, or refactoring React components that fetch data from APIs — especially at scale (recommender carousels, infinite feeds, pages with many parallel fetches, dashboards). Covers request orchestration (parallelism, batching, deduplication), cache strategy (keys, normalization, staleTime, SWR), backend protection (concurrency caps, debounce/throttle, jittered retries, circuit breakers), prefetching (route loaders, hover/intent, idle, server hydration), failure resilience (AbortController, timeouts, error boundaries, stale fallback, idempotent mutations), and feed/carousel patterns (virtualization, cursor pagination, summary/detail split). Trigger even if the user doesn't explicitly mention "performance" or "scale" — any non-trivial React data-fetching code benefits from these patterns. Includes 5 ready-to-use scaffolding templates (resource query hook, carousel data loader, infinite feed, hover-prefetch link, request collapser).
---

# Experimental React Data Fetching & Caching Best Practices

Implementation patterns for React applications that fetch and cache many API requests without overwhelming the backend. **48 rules across 8 categories**, ordered by execution lifecycle impact — earlier categories cascade through everything downstream. Templates show both library-based (TanStack Query, SWR) **and library-free** (pure React + AbortController) implementations so the patterns are usable regardless of stack constraints.

## When to Apply

- Writing or reviewing a React component that calls `fetch`, `useQuery`, `useSWR`, or any data-fetching hook
- Designing a list, feed, or carousel that displays many items each requiring data
- Investigating "the backend is getting hammered" or "the page loads slowly" symptoms
- Choosing between client-side fetching, route loaders, server components, or SSR
- Implementing prefetch, retry, optimistic updates, or any failure-handling logic
- Refactoring code that already does data fetching but with waterfalls, no cache strategy, or no concurrency limits

## Rule Categories by Priority

| # | Category | Impact | Prefix | Rules |
|---|----------|--------|--------|-------|
| 1 | Request Orchestration | CRITICAL | `orch-` | 7 |
| 2 | Cache Strategy | CRITICAL | `cache-` | 7 |
| 3 | Backend Protection | CRITICAL | `protect-` | 7 |
| 4 | Prefetch & Hydration | HIGH | `prefetch-` | 6 |
| 5 | Failure Resilience | HIGH | `resilience-` | 6 |
| 6 | Feed & Carousel Patterns | MEDIUM-HIGH | `feed-` | 7 |
| 7 | Mutation & Invalidation | MEDIUM | `mutate-` | 4 |
| 8 | Component Patterns | MEDIUM | `render-` | 4 |

## Quick Reference

### 1. Request Orchestration (CRITICAL)

- [`orch-parallelize-independent-fetches`](references/orch-parallelize-independent-fetches.md) — Use `Promise.all` for independent requests; never serial `await`
- [`orch-batch-n-plus-one-fanout`](references/orch-batch-n-plus-one-fanout.md) — Collapse per-row fetches via DataLoader-style batching
- [`orch-dedupe-in-flight-requests`](references/orch-dedupe-in-flight-requests.md) — One in-flight request per key, shared across subscribers
- [`orch-lift-fetch-to-route-loader`](references/orch-lift-fetch-to-route-loader.md) — Fetch in parallel with route chunk download
- [`orch-avoid-effect-chains`](references/orch-avoid-effect-chains.md) — Flatten the dependency graph; only true dependencies wait
- [`orch-server-fetch-when-possible`](references/orch-server-fetch-when-possible.md) — Move fetches to RSC/server when they don't depend on client state
- [`orch-prefer-bulk-endpoint-for-fanout`](references/orch-prefer-bulk-endpoint-for-fanout.md) — One bulk request beats N parallel requests

### 2. Cache Strategy (CRITICAL)

- [`cache-deterministic-keys`](references/cache-deterministic-keys.md) — Canonicalize cache keys; strip undefined, sort arrays
- [`cache-normalize-shared-entities`](references/cache-normalize-shared-entities.md) — Store each entity once; views hold references
- [`cache-set-stale-time`](references/cache-set-stale-time.md) — Tune `staleTime` to data volatility, not refresh frequency
- [`cache-stale-while-revalidate`](references/cache-stale-while-revalidate.md) — Render stale instantly; revalidate in background
- [`cache-select-subscribed-fields`](references/cache-select-subscribed-fields.md) — Subscribe to slices, not whole objects
- [`cache-shared-key-factory`](references/cache-shared-key-factory.md) — One typed source of truth for keys; prevents read/write drift
- [`cache-tiered-stale-fresh`](references/cache-tiered-stale-fresh.md) — Different `staleTime` per data class (realtime, fresh, warm, cold, static)

### 3. Backend Protection (CRITICAL)

- [`protect-concurrency-limit-fanout`](references/protect-concurrency-limit-fanout.md) — Cap simultaneous requests with `p-limit`/semaphore
- [`protect-collapse-identical-requests`](references/protect-collapse-identical-requests.md) — In-flight dedup at the fetch layer
- [`protect-debounce-user-driven-fetches`](references/protect-debounce-user-driven-fetches.md) — Wait for user pause before firing search/filter requests
- [`protect-throttle-scroll-triggered`](references/protect-throttle-scroll-triggered.md) — Use IntersectionObserver for viewport-triggered fetches
- [`protect-jittered-retry-backoff`](references/protect-jittered-retry-backoff.md) — Add random jitter to prevent thundering-herd retries
- [`protect-circuit-breaker`](references/protect-circuit-breaker.md) — Stop calling persistently failing endpoints for a cooldown window
- [`protect-rate-limit-aware-client`](references/protect-rate-limit-aware-client.md) — Honor `Retry-After` and `X-RateLimit-*` headers

### 4. Prefetch & Hydration (HIGH)

- [`prefetch-hover-intent-links`](references/prefetch-hover-intent-links.md) — Prefetch on hover/pointerdown for instant navigation
- [`prefetch-parallel-loader-queries`](references/prefetch-parallel-loader-queries.md) — Parallelize independent queries inside loaders
- [`prefetch-hydrate-server-cache`](references/prefetch-hydrate-server-cache.md) — Ship server-fetched data via `HydrationBoundary` / RSC
- [`prefetch-idle-likely-next`](references/prefetch-idle-likely-next.md) — Use `requestIdleCallback` for likely-next data
- [`prefetch-viewport-triggered-next-page`](references/prefetch-viewport-triggered-next-page.md) — Fire next-page prefetch via `rootMargin`
- [`prefetch-budget-and-priority`](references/prefetch-budget-and-priority.md) — Tier prefetches by priority; respect `Save-Data`

### 5. Failure Resilience (HIGH)

- [`resilience-abort-on-unmount`](references/resilience-abort-on-unmount.md) — Forward `AbortSignal` to cancel stale fetches
- [`resilience-bounded-timeouts`](references/resilience-bounded-timeouts.md) — Set per-endpoint timeouts via `AbortSignal.timeout()`
- [`resilience-scoped-error-boundaries`](references/resilience-scoped-error-boundaries.md) — One boundary per data section, not per page
- [`resilience-stale-fallback`](references/resilience-stale-fallback.md) — Render stale cache when fresh fetch fails
- [`resilience-no-auto-retry-mutations`](references/resilience-no-auto-retry-mutations.md) — Use idempotency keys or don't auto-retry
- [`resilience-graceful-degradation`](references/resilience-graceful-degradation.md) — Critical/important/decorative tiers with different failure modes

### 6. Feed & Carousel Patterns (MEDIUM-HIGH)

- [`feed-virtualize-long-lists`](references/feed-virtualize-long-lists.md) — Use TanStack Virtual for lists > 50 items
- [`feed-cursor-pagination`](references/feed-cursor-pagination.md) — Cursors beat offset for inserts and large offsets
- [`feed-split-summary-from-detail`](references/feed-split-summary-from-detail.md) — Carousel summaries lightweight; detail on demand
- [`feed-multi-carousel-isolation`](references/feed-multi-carousel-isolation.md) — Per-carousel error/Suspense boundaries + tiered fallbacks for homepage feeds
- [`feed-stable-keys-across-pages`](references/feed-stable-keys-across-pages.md) — Use entity IDs as keys; never index
- [`feed-image-lazy-and-sized`](references/feed-image-lazy-and-sized.md) — `loading="lazy"` + explicit dimensions
- [`feed-bounded-working-set`](references/feed-bounded-working-set.md) — `maxPages` + entity LRU eviction for unbounded feeds

### 7. Mutation & Invalidation (MEDIUM)

- [`mutate-optimistic-updates-with-rollback`](references/mutate-optimistic-updates-with-rollback.md) — Snapshot, optimistic write, rollback on error
- [`mutate-surgical-invalidation`](references/mutate-surgical-invalidation.md) — Invalidate specific keys, not entire trees
- [`mutate-set-data-over-invalidate`](references/mutate-set-data-over-invalidate.md) — Write mutation responses directly into the cache
- [`mutate-cancel-queries-on-mutate`](references/mutate-cancel-queries-on-mutate.md) — Cancel in-flight queries before optimistic writes

### 8. Component Patterns (MEDIUM)

- [`render-stable-query-keys`](references/render-stable-query-keys.md) — `useMemo` object keys to keep references stable
- [`render-cap-fanout-in-lists`](references/render-cap-fanout-in-lists.md) — Lift fetches; batch via DataLoader; virtualize
- [`render-suspense-per-section`](references/render-suspense-per-section.md) — One Suspense boundary per data section
- [`render-colocate-fetch-with-consumer`](references/render-colocate-fetch-with-consumer.md) — Put `useQuery` next to its consumer, not at the root

## How to Use

1. Open [references/_sections.md](references/_sections.md) for category definitions and impact rationale
2. Read individual rule files for incorrect-vs-correct code examples
3. For ready-to-use scaffolds, see [scaffolding templates](assets/templates/)
4. The [AGENTS.md](AGENTS.md) navigation document (auto-generated) provides a TOC for browsing

## Scaffolding Templates

Six ready-to-adapt code templates under `assets/templates/`:

| Template | Library deps | Purpose |
|----------|-------------|---------|
| `use-resource-query.template.tsx` | TanStack Query | Standard query hook with key factory, retry, abort, optional suspense |
| `use-resource-query-no-deps.template.tsx` | **None** (pure React + AbortController) | Same patterns as above, library-free: hand-rolled cache, dedup, retry with jitter, staleTime/gcTime, concurrency limit |
| `carousel-data-loader.template.tsx` | TanStack Query + react-error-boundary | Single carousel (summary + viewport-triggered detail) *and* multi-carousel feed with per-carousel failure isolation |
| `infinite-feed.template.tsx` | TanStack Query + TanStack Virtual | Cursor-paginated infinite feed with virtualization and bounded working set |
| `prefetch-link.template.tsx` | None | Hover/intent prefetch link wrapper |
| `request-collapser.template.ts` | None | In-flight deduplication + concurrency limit utility |

**Library-free path:** if you can't add TanStack Query / SWR / DataLoader to your bundle (size constraints, host-app conflicts, dependency bans), start with `use-resource-query-no-deps.template.tsx` — it implements the core cache/dedup/retry/abort patterns in ~250 lines using only React and the web platform. The other templates that depend on TanStack can be adapted on top of it; only the `request-collapser` and `prefetch-link` templates are zero-dep out of the box.

## Reference Files

| File | Description |
|------|-------------|
| [references/_sections.md](references/_sections.md) | Category definitions, ordering, impact rationale |
| [assets/templates/_template.md](assets/templates/_template.md) | Template for authoring new rules |
| [metadata.json](metadata.json) | Version, references, abstract |

## Related Skills

- `react-optimise` — General React render performance (this skill is data-fetching-specific)
- `nextjs-bundle-optimizer` — Bundle/payload optimization for Next.js
- `inngest-nextjs-patterns` — Server-side workflow patterns (complements server-fetch guidance)
