plugins/frontend-toolkit/skills/api-caching-optimization/SKILL.md
Reduce duplicate/over-fetching requests, tune cache policies by freshness, invalidate caches after mutations, and shape queries. Use when duplicate API requests appear, stale data shows after a write, TTFB exceeds 500ms, or before shipping. Not for route-level rendering strategy (use render-strategy-decision) or Core Web Vitals tuning (use rendering-performance).
npx skillsauth add jaykim88/claude-ai-engineering api-caching-optimizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Eliminate redundant network requests, pick the right cache policy per data type, and shape queries to fetch only what's needed. Faster responses, lower server cost, fewer cache-staleness bugs.
Universal — fetch-deduplication, cache-policy classification, HTTP Cache-Control headers, query shaping, and pagination apply to any client; only the data-layer library API differs.
Audit data-layer fetch policies by freshness need
render-strategy-decision for route-level cache / revalidate config that must align with these policies.Invalidate / update the cache after every mutation
invalidateQueries / optimistic setQueryData, Next revalidateTag / revalidatePath)Eliminate duplicate fetches and request waterfalls
await a; await b) → run in parallel (Promise.all); dependent/nested fetches that serialize → hoist or preload so they start early. Waterfalls, not payload size, are often the real TTFB/latency cause.Set HTTP Cache-Control headers
Cache-Control: public, max-age=31536000, immutableCache-Control: no-cache, must-revalidateCache-Control: private, no-storeCache-Control: public, max-age=60, stale-while-revalidate=300Shape data-layer queries
*), paginate (don't fetch unbounded rows — strategy in step 7), verify index usage on slow queries. (see Implementation; Supabase select('id,name') / .range() / EXPLAIN ANALYZE)Add dns-prefetch / preconnect for external origins
<link rel="preconnect" href="https://..."> in headImplement pagination / infinite scroll for large lists
fetch('/api/items') and return 10,000 rowsRely on the framework's request-level fetch dedup
Verify (validation loop)
| Tier | Examples | Action SLA |
|---|---|---|
| Critical | User-specific/sensitive data served from a shared/public cache (Cache-Control: public on private responses); always-fresh data (mutation-adjacent reads) served stale, misleading the user | Block release; fix immediately |
| Major | Duplicate fetches of the same resource per render; TTFB > 500ms (dynamic) / > 200ms (static); select('*') in production; unbounded list fetch (no pagination) | Fix this sprint |
| Minor | Missing preconnect/dns-prefetch for external origins; suboptimal staleTime/TTL tuning; offset pagination where cursor would be more stable | Schedule within 2 sprints |
select *dns-prefetch/preconnect setfetchPolicy / TanStack staleTime)next.config.ts headers() function (or equivalent) — one route pattern per directiveperf(db): select only needed columns for <query>preconnect/dns-prefetch links in root layoutqueryClient.invalidateQueries / optimistic setQueryData (+ rollback); Next App Router revalidateTag / revalidatePath on the Data Cache (persistent — distinct from per-render Request Memoization). Next App Router has 4 cache layers: Request Memoization, Data Cache, Full Route Cache, Router Cachenext.config.ts headers() function or middlewarecache-first (default) — static/rarely-changing; cache hit returns with no networkcache-and-network — fresh-but-instant; pair with nextFetchPolicy: 'cache-first' or every re-render refetchesnetwork-only — always-fresh; skips cache read but still writes to cacheno-cache — never-persist; no read, no writecache-only — derived views that assume a parent already fetched (throws on miss)staleTime per query type (not globally); SWR: revalidateOnFocus / dedupingIntervalselect('id, name') + indexes + EXPLAIN ANALYZEuseFetch and $fetch have built-in caching; Nuxt's useAsyncData provides server-component-style dedup; TanStack Query has Vue bindings+page.ts load() runs server-side with built-in dedup; +server.ts API routes get HTTP cache headers explicitlyshareReplay for in-memory dedupCache-Control (immutable / no-cache / private / stale-while-revalidate) is HTTP standard — works for any client; preconnect / dns-prefetch are HTML standardrender-strategy-decision — route-level cache/revalidate config must align with fetch policiesstate-management-decisions — when server state lives in the wrong storerendering-performance — TTFB and waterfalls affect LCPcache-first is wrong for frequently-changing data — use cache-and-network when freshness matters but instant render is required; reserve network-only for mutation-adjacent reads. In Next.js Server Components, Request Memoization deduplicates same-input fetches within a single render for free — verify your fetch wrappers don't defeat it.development
Audit and optimize third-party scripts — analytics, tag managers, chat widgets, embeds — with the right loading strategy, performance budget, facades, and CSP/consent controls. Use when adding a script, when TBT/INP regress, when a GDPR/CCPA consent requirement arises, or before shipping. Not for first-party bundle size (use bundle-optimization) or broad Core Web Vitals diagnosis (use rendering-performance).
development
Apply the Testing Trophy (mostly integration tests with RTL + MSW, sparing E2E with Playwright) and set coverage thresholds. Use before new feature work, after bug fixes, when CI coverage falls below target, or when tests are flaky or break on every refactor. Not for wiring coverage gates + Playwright into the GitHub Actions matrix (use cicd-pipeline) or auditing WCAG a11y compliance (use accessibility-audit).
development
Inventory and prioritize technical debt — TODO/FIXME/HACK, any usage, deprecated APIs, untested logic — with impact × effort matrix. Use at quarter start, before a refactoring sprint, when a new teammate joins, or when feature velocity slows. Not for actually paying down debt (use code-refactoring) or recording a migration approach (use decision-records) — this only inventories and prioritizes.
development
Decision framework for choosing the right state location — URL, server cache, local component, or shared/global store. Use when state-sync bugs appear, prop drilling gets deep (3+ levels), filters/tabs lose state on reload, or quarterly review. Not for form state specifically (use form-ux) or when the state is actually server data (use api-caching-optimization).