← library
skill🕸️ Web appsv1 · updated 2026-09-10

Next.js Cache Migration

Migrates a Next.js App Router app to explicit caching — enabling cacheComponents and partialPrefetching, auditing implicit-caching assumptions, adding 'use cache' and Suspense shells, and locking behavior with navigation tests. Use when upgrading an app to Next.js 16.x or enabling Instant Navigations on an existing project.

Run it as a prompt

Paste this into any AI agent, or fetch it: curl -s https://uplift.page/api/v1/prompts/nextjs-cache-migration/raw

prompt.md
# Next.js Cache Migration

Next.js 16.3's Instant Navigations model (`cacheComponents` + `partialPrefetching`)
makes caching explicit and is slated to become the default in a future major. Migrating
early is cheap; migrating under a forced major upgrade is not. This is the runbook.

## When to use

- Upgrading a client app to Next.js 16.x
- Enabling `cacheComponents` / Instant Navigations on an existing App Router app
- A navigation-speed complaint on an app still relying on implicit caching

## Steps

1. **Baseline first.** Record current behavior for the key routes: what is static,
   what streams, navigation timings. Surprises during migration get judged against
   this, not memory.
2. **Flip the flags on a branch**: `cacheComponents: true` and
   `partialPrefetching: true` in `next.config.ts`. Build — the errors are the audit:
   every route that silently depended on implicit caching now says so.
3. **Make caching explicit route by route**: add `'use cache'` to work that is
   cacheable (it now works client-side too); give every dynamic route a Suspense
   loading shell so navigations paint the static frame instantly while dynamic holes
   stream in.
4. **Audit the assumptions the errors didn't catch**: fetches that assumed automatic
   dedupe/caching, route handlers assumed static, anything reading cookies/headers
   inside a would-be-cached scope — move the dynamic read below the Suspense boundary
   or out of the cached function.
5. **Lock it with tests**: navigation tests asserting the shell paints before data
   (Playwright's `instant()` helper where available), plus one test per route that
   must stay dynamic.
6. **Compare against the baseline** and ship route group by route group — not the
   whole app in one merge.

## Rules

1. Never enable the flags and ship on a green build alone — the failure mode is stale
   data rendered confidently, which no build error catches.
2. Every `'use cache'` gets a deliberate revalidation story (time, tag, or
   on-mutation); "cached forever by accident" is the migration's main bug.
3. Personalization goes below a Suspense boundary, not into the cached shell —
   auth-dependent UI in a cached component is a data leak, not a perf win.
4. Document per route group: cached / streamed / fully dynamic, and why.

Install it as a skill

Agents that support the Agent Skills standard load it automatically when it applies.

download
curl -fsSL https://uplift.page/p/nextjs-cache-migration/SKILL.md --create-dirs -o .agents/skills/nextjs-cache-migration/SKILL.md
or from any MCP client
MCP server: https://uplift.page/mcp
Tool: pull_skill  {"slug": "nextjs-cache-migration"}

The full skill

Next.js 16.3's Instant Navigations model (cacheComponents + partialPrefetching) makes caching explicit and is slated to become the default in a future major. Migrating early is cheap; migrating under a forced major upgrade is not. This is the runbook.

When to use

  • Upgrading a client app to Next.js 16.x
  • Enabling cacheComponents / Instant Navigations on an existing App Router app
  • A navigation-speed complaint on an app still relying on implicit caching

Steps

  1. Baseline first. Record current behavior for the key routes: what is static, what streams, navigation timings. Surprises during migration get judged against this, not memory.
  2. Flip the flags on a branch: cacheComponents: true and partialPrefetching: true in next.config.ts. Build — the errors are the audit: every route that silently depended on implicit caching now says so.
  3. Make caching explicit route by route: add 'use cache' to work that is cacheable (it now works client-side too); give every dynamic route a Suspense loading shell so navigations paint the static frame instantly while dynamic holes stream in.
  4. Audit the assumptions the errors didn't catch: fetches that assumed automatic dedupe/caching, route handlers assumed static, anything reading cookies/headers inside a would-be-cached scope — move the dynamic read below the Suspense boundary or out of the cached function.
  5. Lock it with tests: navigation tests asserting the shell paints before data (Playwright's instant() helper where available), plus one test per route that must stay dynamic.
  6. Compare against the baseline and ship route group by route group — not the whole app in one merge.

Rules

  1. Never enable the flags and ship on a green build alone — the failure mode is stale data rendered confidently, which no build error catches.
  2. Every 'use cache' gets a deliberate revalidation story (time, tag, or on-mutation); "cached forever by accident" is the migration's main bug.
  3. Personalization goes below a Suspense boundary, not into the cached shell — auth-dependent UI in a cached component is a data leak, not a perf win.
  4. Document per route group: cached / streamed / fully dynamic, and why.

Examples

Good: "Marketing routes: fully cached, revalidate daily. Dashboard: static shell +
       streamed org data below Suspense; instant() test pins the shell paint."
Bad:  Flags on, build green, shipped — pricing page now shows yesterday's prices
      and nothing failed.

Served from the uplift.page library and refreshed within 5 minutes of every update.