nextjs-cache-architecture

Use this skill whenever the user wants to design or implement caching in a Next.js 16+ App Router project — setting up the "use cache" directive, building a cache tag registry, wiring mutations to invalidation utilities, structuring Suspense boundaries for partial prerendering, handling personalized

By mohamed-hossam1 · 4,036 installs

npx skills add mohamed-hossam1/nextjs-skills --skill nextjs-cache-architecture

Source repository · Upstream listing

Next.js Cache Architecture Architect caching in a Next.js 16+ App Router project from day one — not just dropping "use cache" where it happens to fit, but structuring the tag registry, revalidation utilities, Suspense boundaries, and mutation wiring so the cache stays correct as the codebase grows. How to use this skill Apply every rule and template below to the user's actual project. Replace placeholders like [Entity] and [collection] with names from their codebase before writing any code. Where to look next Most implementations only need this file. Load a reference when the task calls for it. If the user is... Read Asking how cache keys are derived, what cacheLife profiles mean, or hitting a "use cache" limitation references/core concepts.md Caching anything that depends on a logged in user references/personalized content.md Reporting stale data, or doing a final review pass references/debugging and checklist.md Migrating an existing codebase off unstable cache references/migration from unstable cache.md Drop in templates in assets/ (rename placeholders to match the user's codebase): assets/tags.ts → lib/cache/tags.ts assets/revalidate.ts → lib/cache/revalidate.ts assets/SuspenseOnSearchParams.tsx → components/SuspenseOnSearchParams.tsx The architecture in one breath A correct cache implementation has three load bearing pieces. Build all three on day one — adding them later is much harder than getting them right up front. 1. Tag registry ( lib/cache/tags.ts ) — every tag string lives here. No raw strings anywhere else. 2. Revalidation utilities ( lib/cache/revalidate.ts ) — every updateTag() lives here. Mutations import from this file. 3. Cache placement on data, not on pages — "use cache" goes on data fetching functions or cached child components. Page components orchestrate Suspense boundaries; the children fetch. Once those three are in place, the rest is just applying them consistently. Step 1 — Enable Cache Components Step 2 — Build the cache tag registry File: lib/cache/tags.ts (template: assets/tags.ts ) Use the assets/tags.ts template. The as const satisfies TagRegistry shape gives literal types and rejects malformed entries at compile time. Step 3 — Build revalidation utilities File: lib/cache/revalidate.ts (template: assets/revalidate.ts ) All updateTag() calls live here. Mutations import these functions — they never call updateTag() directly. Step 4 — Implement data fetching Place "use cache" in data fetching functions. Never fetch inside page components — page components orchestrate, they do not fetch. Step 5 — Structure rendering boundaries Every page follows this shape: Standard page Dynamic route page Filtered / search params page A standard <Suspense does not re trigger its fallback on client side navigation when only searchParams changes. Use SuspenseOnSearchParams (template: assets/SuspenseOnSearchParams.tsx ) on every page with search or filter params. Step 6 — Handle personalized content Read cookies() / headers() / auth() outside the cache boundary and pass the value as a prop. The argument becomes part of the auto generated cache key, so each user gets their own entry. Calling any of those APIs inside a "use cache" function throws or produces wrong behavior. See references/personalized content.md for the full read outside / cache inside pattern and the rare "use cache: private" exception. Step 7 — Wire mutations to invalidation Mutations call revalidation utilities and never reach for updateTag() themselves. This keeps the cache layer mechanical and auditable from one file, and lets you add observability (logging, tracing) in one place. updateTag vs revalidateTag Two APIs for two different needs: API Effect Call from updateTag(tag) Immediate — the same request sees fresh data Server actions, via revalidate.ts revalidateTag(tag, "max") Background stale while revalidate — next request sees fresh data Route handlers, webhooks revalidateTag always takes a second argument ( "max" for stale while revalidate, { expire: 0 } for immediate hard expiry). The single argument form is deprecated and silently does nothing in some configurations. Common mistakes When the cache misbehaves, walk these in order. The first six catch nearly everything; only run next build after the rest pass. The full debug walk and a sign off checklist are in references/debugging and checklist.md . Symptom or smell Fix Function runs uncached on every request "use cache" is after an await — move it to be the first statement. Cached function throws or returns wrong data per user Move cookies() / headers() / auth() outside; pass values as arguments. updateTag does nothing Tag string typo, or no cacheTag ever registered the matching tag. Mutation completes but the list still reads stale Revalidation utility called before the write, or not called at all. Whole page re renders even though only one section changed A dynamic child sits inside a cached parent — split with <Suspense . Filter UI doesn't show a loading state on navigation Plain <Suspense — switch to SuspenseOnSearchParams . Page marked dynamic when you expected static Run next build ; trace the leaked dynamic API in the route's source tree. Page component fetches data directly Move the fetch into a cached child; pages should orchestrate, not fetch. For the full debug walk and a sign off checklist, see references/debugging and checklist.md . To verify the static parts of a finished implementation against the user's project, run scripts/audit.mjs <project root — usage and what it checks are documented in README.md .