Claude Cursor GitHub Copilot Skill

nextjs-rendering-caching-review

Statically review Next.js App Router route segments and fetch() calls for rendering-mode (static/ISR/dynamic) and Data-Cache misconfiguration, escalating cross-user data leakage to a security finding rather than a performance nit.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vincentchuwaichow-vanguard-frontier-agentic-skills_frontend_nextjs-rendering-caching-review-febe32a.zip · 11 KB
Part of vincentchuwaichow/vanguard-frontier-agentic — 293 skills

Install

skills CLI npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/nextjs-rendering-caching-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
Git git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Next.js Rendering & Caching Review

Purpose

Review Next.js App Router rendering-mode selection (static / ISR / dynamic) and fetch() / Data-Cache configuration without re-litigating component architecture, styling, or Pages Router APIs in every response. This skill exists so caching-staleness and cross-user data-leakage risk stay the focus, and so those adjacent concerns stay out of scope.

When to use

Use this skill when the user asks to:

  • review caching/revalidation behavior in an App Router PR,
  • investigate a report of stale data being served,
  • investigate a report of one user seeing another user's data,
  • decide whether a route should be static, ISR, or dynamic.

Do not use this skill for:

  • Pages Router codebases (getStaticProps / getServerSideProps) — different API surface; do not apply App Router fetch()-cache guidance to it,
  • purely client-side SWR/React Query caching with no server fetch() involved,
  • component decomposition or state-placement review — that is react-component-architecture-review.

Context7 Documentation Protocol

  • Resolve /vercel/next.js with resolve-library-id before citing any caching-default claim.
  • Before asserting a fetch() caching default, read the repo's package.json to confirm the installed Next.js major version, then call query-docs scoped to that version. Next's fetch() caching default changed between Next 14 (cache: 'force-cache' default) and Next 15 (uncached by default; GET Route Handlers also uncached by default). A default claim verified against one major must never be reused for another.
  • If the repo has adopted the use cache / Cache Components model (Next 15.x canary / Next 16 opt-in via the top-level cacheComponents config, which replaced the removed experimental.dynamicIO/experimental.useCache flags), treat that as a distinct caching paradigm from the classic fetch()-options model — do not mix cacheLife/cacheTag guidance with classic next: { revalidate, tags } guidance in the same finding without confirming which model the route actually uses.
  • revalidateTag(tag) accepts a second, version-sensitive options.profile argument in newer releases: profile: "max" marks the tag stale for background stale-while-revalidate on next visit (the currently recommended pattern); omitting it schedules an immediate expire-on-next-request, which current docs mark as deprecated in favor of profile: "max" or updateTag. Confirm which signature the installed version supports via query-docs before recommending one — do not assume the two-argument form exists on an older major.
  • If Context7 is unavailable, fall back to the official_docs URLs in this skill's metadata.json and label the claim documentation-based, unverified against current release.

Lean operating rules

  • First read package.json to confirm the installed Next.js major version and whether the deployment target is Vercel or self-hosted. Do not assert a caching default or a platform-specific cache primitive (e.g. Vercel Data Cache persistence across deployments) without confirming both.
  • Classify every in-scope route segment as static, ISR, or fully dynamic before evaluating its fetch() calls. A route with no revalidate export, no dynamic export, and no dynamic API (cookies(), headers(), searchParams) usage defaults to static; do not assume dynamic without evidence.
  • Treat cross-user Data Cache leakage — a per-user or session-scoped response cached as if it were shared/public — as a HIGH-severity security finding requiring security-review sign-off, not a caching-strategy suggestion. This is the hard security gate for this skill.
  • Do not recommend export const dynamic = 'force-dynamic' on a whole route to fix a leakage finding without first checking whether a scoped cache: 'no-store' on the offending fetch() call, or a user-scoped cache tag/key, is sufficient. Route-wide force-dynamic is a real TTFB/cost overcorrection.
  • Do not conflate Request Memoization (per-render dedup of identical fetch() calls, scoped to a single render pass) with the Data Cache (persists across requests/deployments). A finding that treats memoization as if it persisted across users is wrong on its face.
  • Do not treat every dynamic route as a defect. Routes that genuinely require per-request data (auth-gated dashboards, personalized content) are correctly dynamic; only flag dynamic classification when the same correctness could be achieved with static or ISR.
  • Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only).
  • Treat any hardcoded API key, token, session secret, or credential found in a fetch() call, header, or example data as a HIGH-severity finding requiring immediate escalation, not a caching note.

References

Load these only when needed:

  • Review workflow and findings contract — use for the step-by-step review procedure, the rendering-mode classification table, the leakage decision tree, and the required output shape.
  • ISR reference — load only for routes using generateStaticParams + revalidate, or on-demand revalidation via revalidatePath/revalidateTag.
  • Cache-tag invalidation reference — load only when tag-based invalidation (next: { tags }, revalidateTag) is present in the diff.

Response minimum

Return, at minimum:

  • per-route rendering-mode table (route, mode, justification),
  • ranked caching findings with file:line, risk class, and fix,
  • the Next.js major version the claims were verified against,
  • verdict: approve / approve-with-notes / block,
  • evidence level and open questions.
Files (vanguard-frontier-agentic)
  • references
    • cache-tag-invalidation.md 5 KB
      # Cache-tag invalidation reference (next: { tags }, revalidateTag)
      
      Use this reference only when the diff includes `next: { tags: [...] }` on a `fetch()` call, or a `revalidateTag`/`revalidatePath` call. Do not load it for routes with no tag-based invalidation.
      
      ## What people get wrong
      
      The naive story is:
      
      > Tag it, call `revalidateTag` on mutation, done — invalidation is invalidation.
      
      Wrong. Tag scope is a design decision with the same over-/under-invalidation failure modes as cache-key design anywhere else: too coarse a tag invalidates far more cached data than the mutation actually changed (cache-hit-rate and cost regression across unrelated routes); too fine or simply mismatched a tag misses the mutation entirely (stale-data bug that looks like "the cache is broken" but is actually "the tag was never invalidated"). Official Next.js guidance itself states a preference for tag-based revalidation over path-based (`revalidatePath`) specifically because it is more precise — but precision only helps if the tag design matches the actual data-ownership boundaries.
      
      ## Officially grounded shape
      
      - `fetch(url, { next: { tags: ['posts'] } })` associates that cached entry with the tag `'posts'`. Multiple unrelated `fetch()` calls can share a tag; a single `fetch()` call can carry multiple tags.
      - `revalidateTag(tag)` (imported from `next/cache`, called from a Server Action or Route Handler) invalidates **every** cached entry carrying that tag, forcing a re-fetch on the next request to any route that reads it — not just the route that triggered the mutation.
      - `revalidatePath(path)` invalidates all cached data for a specific route path, without needing to know which tags are attached to it. Official guidance recommends preferring tag-based revalidation over path-based when the tag structure is known, precisely because path-based invalidation is coarser and easier to over-invalidate with.
      - Route Handlers are a common on-demand-revalidation entry point (e.g., a webhook receiver calling `revalidateTag` in response to an external CMS publish event) — verify any such handler is itself access-controlled (secret/token check) so an unauthenticated caller cannot trigger arbitrary cache invalidation (a cost/DoS-adjacent concern, not just a staleness one).
      
      ## Non-negotiable design rules
      
      1. **Tag granularity should match data-ownership boundaries, not convenience.** A single `'posts'` tag shared by every post's detail page means updating one post's title invalidates every post's cached page. If the mutation is scoped to one entity, prefer a per-entity tag (`post-${id}`) plus a broader list-level tag (`posts`) only where the list view genuinely needs to reflect the change too — and invalidate both explicitly when both are affected.
      
      2. **Every mutation that changes tagged data must call the matching invalidation, and only that data's tag.** Trace each `revalidateTag`/`revalidatePath` call back to the mutation that triggers it; confirm the tag argument matches a tag actually used by a `fetch()` in the affected route, not a stale or copy-pasted tag name from an unrelated feature.
      
      3. **On-demand revalidation entry points (webhook/API routes) must be access-controlled.** An unauthenticated `revalidateTag`/`revalidatePath` endpoint lets any caller force full cache invalidation on demand — flag as a MEDIUM-or-higher availability/cost finding if no secret/token/signature check gates the handler.
      
      4. **Do not use tag-based invalidation as a substitute for per-user cache scoping.** Tags invalidate shared cache entries; they do not make a shared cache entry safe to serve to multiple users. If the underlying leakage concern from SKILL.md's hard security gate applies (per-user data cached without a `no-store`/scoped key), adding a tag does not resolve it — the fetch still needs `cache: 'no-store'` or a genuinely user-scoped cache key, independent of tagging.
      
      ## Minimal safe verification flow
      
      1. List every tag used across in-scope `fetch()` calls and every `revalidateTag`/`revalidatePath` call in the diff.
      2. For each mutation, confirm it invalidates exactly the tags that cover data it changed — no more, no less.
      3. For each on-demand-revalidation Route Handler, confirm it is access-controlled.
      4. Confirm no leakage concern is being "fixed" with tagging alone when it actually requires `no-store` or per-user cache-key scoping.
      
      ## When to push back
      
      Push back if the user asks for:
      
      - one broad tag applied to "everything on this page" to avoid designing per-entity tags — that guarantees over-invalidation as the app grows,
      - an on-demand revalidation Route Handler with no auth check "since it's just a cache refresh" — uncontrolled invalidation is a cost and availability surface, not a harmless refresh,
      - using `revalidateTag` as the fix for a reported "user A sees user B's data" bug — that is a caching-scope/leakage bug, not a staleness bug, and tagging does not address it.
      
      Those are not shortcuts. They convert a precise invalidation model into either a blunt one or a papered-over security defect.
      
    • isr-reference.md 5.6 KB
      # ISR reference (generateStaticParams + revalidate)
      
      Use this reference only for App Router routes using `generateStaticParams` combined with a time-based `revalidate` export or `next: { revalidate }` fetch option. Do not apply this to Pages Router `getStaticProps`/`getStaticPaths` — that is a different, legacy API surface; if you encounter it, note it as out-of-scope for this skill rather than translating App Router guidance onto it.
      
      ## What people get wrong
      
      The naive story is:
      
      > `revalidate: 60` means the page is rebuilt every 60 seconds, so pick a number and move on.
      
      Wrong. `export const revalidate = 60` (or `next: { revalidate: 60 }` on the underlying `fetch()`) means: Next.js will attempt to re-generate the page in the background **at most once every 60 seconds, and only when a request comes in** after that window — it is not a background cron. Until regeneration completes, the **stale** cached version continues to be served (stale-while-revalidate), not a loading state and not a 404. If regeneration throws an uncaught error, Next.js does **not** invalidate the currently-shown page; it keeps serving the last successfully generated version and retries on the next request. This error-handling behavior matters for the review: a `revalidate`-backed route that silently swallows fetch errors can serve indefinitely-stale data without ever surfacing a failure, because the fallback is "keep serving the old page," not "fail loudly."
      
      ## Officially grounded shape
      
      - `generateStaticParams` defines which dynamic-segment values (`params`) get prerendered at build time; segments not returned by it are generated on first request (or 404, depending on `dynamicParams` config) and then cached per the route's `revalidate` value.
      - `export const revalidate = N` sets the route-level regeneration interval in seconds. A `fetch()` call inside the route can independently set its own `next: { revalidate: N }` — the **effective** revalidation window for the route is the **minimum** of the route-level export and any fetch-level values that feed the rendered output. Do not evaluate only the route-level export and ignore a shorter fetch-level value (or vice versa).
      - On-demand revalidation (`revalidatePath`, `revalidateTag`) supersedes the time-based window — it forces the next request to regenerate regardless of how much of the interval has elapsed. A route using only on-demand revalidation with no `revalidate` export is valid (fully static until an explicit invalidation call) — do not flag the absence of a time-based `revalidate` as a defect when on-demand invalidation is present and wired to the actual mutation path.
      
      ## Non-negotiable design rules
      
      1. **Trace the effective revalidation window to the shortest contributing value.** If the route export says `revalidate = 3600` but one `fetch()` inside it uses `next: { revalidate: 60 }`, the route effectively refreshes at the 60-second cadence for that data. Flag a review that only cites the route-level export as incomplete.
      
      2. **Confirm error handling does not silently extend staleness beyond intent.** If `getData()` swallows a non-2xx response (e.g. returns a default/cached object instead of throwing), a backend outage will not trigger the "keep serving last good page and retry" fallback correctly — verify the fetch call surfaces failures (throws or returns a non-ok response Next.js can detect) rather than masking them.
      
      3. **Verify `generateStaticParams` coverage matches the actual param space, or that `dynamicParams` is deliberately configured.** Undercovering high-traffic params with `dynamicParams: false` produces 404s for legitimate paths; overcovering a huge param space with build-time generation can blow up build time — this is a build-cost finding, not correctness, but should still be flagged as MEDIUM if extreme.
      
      4. **Do not apply ISR guidance to per-user data.** ISR caches are shared across all requests to that path/param combination. If the page under `generateStaticParams` renders user-specific content (e.g., `/dashboard/[userId]` returning that user's private data) and relies on route-level `revalidate` without a `no-store`/dynamic escape hatch for the personalized parts, this is the cross-user leakage pattern from SKILL.md's hard security gate — escalate to HIGH, not a normal ISR tuning note.
      
      ## Minimal safe verification flow
      
      1. Confirm the Next.js major version (ISR semantics referenced here are for App Router, current stable behavior; verify no `use cache`/Cache Components migration has changed the model — see SKILL.md's Context7 protocol).
      2. Locate every `revalidate` export and every `next: { revalidate }` fetch option in the route; compute the effective window.
      3. Confirm `generateStaticParams` scope is intentional (full enumeration vs. partial + `dynamicParams`).
      4. Confirm fetch error handling doesn't mask backend failures.
      5. Confirm no per-user data flows through a route relying on shared ISR caching without a scoped escape hatch.
      
      ## When to push back
      
      Push back if the user asks for:
      
      - a single global `revalidate` value applied uniformly "to keep it simple" across routes with very different freshness requirements — that produces either unnecessary staleness or unnecessary regeneration load,
      - ISR applied to a route serving authenticated, per-user data with no scoped `no-store`/dynamic fetch for the personalized parts,
      - removing error handling from the data-fetch function "to simplify the code" — that removes the safety net that keeps a broken backend from ever surfacing as a build/render failure.
      
      Those are not simplifications. They trade away either freshness guarantees or the leak/error-visibility safety net ISR depends on.
      
    • workflow-and-output.md 7.6 KB
      # Review workflow and findings contract
      
      Use this reference for the full rendering/caching review procedure and the required output shape.
      
      ## What people get wrong
      
      The naive story is:
      
      > Static is faster, dynamic is safer for personalized data, so just eyeball the route and guess.
      
      Wrong. Rendering mode and cache configuration are decided per `fetch()` call, not just per route — a nominally "static" route can still leak per-user data if one of its `fetch()` calls defaults to `force-cache` on a response that embeds session-scoped content. Conversely, a route marked `force-dynamic` "to be safe" pays full per-request render cost even when only one of its five `fetch()` calls actually needs freshness. The review has to operate at both the route level and the individual `fetch()`-call level, and it has to separate two independently-varying axes: **staleness** (is this data fresh enough) and **audience** (is this response safe to share across users).
      
      ## Workflow
      
      1. **Confirm the Next.js major version and deployment target**
         - Read `package.json` for the exact Next.js version. Read hosting config (`next.config.js`, deployment docs, CI) for Vercel vs self-hosted.
         - Do not proceed to assert a caching default before this step; Next 14 and Next 15 have different `fetch()` defaults (see Context7 Documentation Protocol in SKILL.md).
      
      2. **Classify each route segment's rendering mode**
         - **Static**: no `dynamic` export or `dynamic = 'auto'`/`'force-static'`, no dynamic APIs (`cookies()`, `headers()`, `searchParams` read at render), and no `fetch()` call configured `no-store` or tagged with per-request freshness.
         - **ISR**: has `export const revalidate = <seconds>`, or at least one `fetch()` using `next: { revalidate: N }`, combined with `generateStaticParams` for dynamic segments.
         - **Dynamic**: has `export const dynamic = 'force-dynamic'`, uses `cookies()`/`headers()`/uncached `searchParams`, or has a `fetch()` using `cache: 'no-store'` that determines the response.
         - Record the exact evidence (file:line) backing each classification. Do not infer mode from route naming or folder structure.
      
      3. **Extract every `fetch()` call's cache configuration**
         - Note `cache: 'force-cache' | 'no-store'`, `next: { revalidate, tags }`, and whether the option is explicit or relying on the version's default.
         - Cross-reference against the confirmed Next.js major (step 1) to know what "no option specified" actually means for that call.
      
      4. **Cross-reference data sensitivity against cache scope**
         - For each `fetch()` call, determine whether the fetched or returned data is per-user/session-scoped: does it forward an `Authorization` header, a session cookie, or a user ID from `cookies()`/`headers()`/route params into the request or the response shape?
         - If yes, and the call uses the default/`force-cache` behavior with no user-scoped cache key or tag differentiating it per user, this is a **leakage candidate** — proceed to the decision tree below.
      
      5. **Check `revalidateTag`/`revalidatePath` scope**
         - Confirm each invalidation call targets exactly the tag/path that changed. A `revalidateTag('posts')` call fired from an endpoint that only mutates one post's title is over-broad if other tags exist per-entity; a call that never fires after the relevant mutation is under-invalidation (stale-data risk).
      
      6. **Produce ranked findings**
         - Order by blast radius: cross-user data leakage first (always HIGH, security sign-off required), then correctness/staleness defects, then avoidable performance cost (unnecessary `force-dynamic`), then lower-severity notes.
      
      ## Decision tree
      
      - `fetch()` call includes `Authorization`, forwards a session cookie, or otherwise returns per-user data **AND** uses the version's default/`force-cache` behavior with no `no-store` and no user-scoped tag/key → **HIGH: cross-user leakage risk.** Escalate per SKILL.md's hard security gate — this is a security finding, not a caching-strategy note.
      - Route needs freshness within N seconds and currently has no `revalidate`/tag strategy → recommend ISR with the appropriate `revalidate` value or `next: { revalidate: N }` on the relevant `fetch()` calls. Do not default to blanket `force-dynamic`.
      - Route is fully static content but marked `force-dynamic` (or has an unnecessary `no-store` fetch) → flag as avoidable TTFB/hosting cost; recommend the minimal-scope fix (fix the one `fetch()` call, not the whole route, unless every call genuinely needs it).
      - `revalidateTag`/`revalidatePath` scope is broader than the mutation that triggered it → flag as over-invalidation (cache-hit-rate/cost regression). Scope narrower than the mutation → flag as stale-data risk.
      
      ## Output contract
      
      Return:
      
      1. Next.js major version and deployment target confirmed (or explicitly noted as unconfirmed)
      2. Per-route rendering-mode table: route | mode | evidence (file:line) | justification
      3. Ranked findings, each with:
         - file:line evidence
         - risk class (cross-user leakage / staleness / avoidable dynamic cost / over- or under-invalidation)
         - concrete fix, scoped to the narrowest sufficient change
         - severity (HIGH / MEDIUM / LOW)
         - evidence level (`repo evidence`, `documentation-based`, `inference`)
      4. Verdict: approve / approve-with-notes / block
      5. Open questions or explicitly out-of-scope items (e.g. Pages Router files encountered, or unconfirmed deployment target)
      
      ## Validation gates
      
      - Every caching-default claim states the Next.js major version it was verified against.
      - Every cross-user-leakage finding identifies exactly which data field is at risk and why the current cache configuration would serve it to another user — not just "this looks user-specific."
      - No finding assumes Vercel-specific cache behavior (e.g. Data Cache persistence across deployments, on-demand ISR via the Vercel API) for a self-hosted deployment without confirming the deployment target first.
      - No leakage finding is downgraded from HIGH severity for "code cleanliness" reasons; the security-notes hard gate applies regardless of how minor the fix looks.
      
      ## Common failure modes
      
      - Assuming Next 13/14 caching defaults (`fetch()` cached by default) on a Next 15+ codebase, or vice versa — always version-check first.
      - Treating all dynamic rendering as a defect, ignoring that some routes genuinely require per-request data.
      - Conflating Request Memoization (dedup within one render pass) with the Data Cache (cross-request persistence) — a memoized call is not a caching-leakage risk across users because it does not persist past the single render.
      - Recommending route-wide `force-dynamic` as the default fix for a single-`fetch()` leakage finding, incurring unnecessary TTFB cost on the rest of the route.
      
      ## Adversarial checklist
      
      Before finalizing a finding, answer these:
      
      - Does any `fetch()` call returning per-user data lack an explicit `no-store` or a user-scoped cache tag/key?
      - Is the caching-default claim matched to the actual Next.js major version in `package.json`, not assumed from general familiarity with "Next.js"?
      - Does a `revalidateTag`/`revalidatePath` call risk invalidating (or under-invalidating) data unrelated to, or needed by, the change that triggered it?
      - Is a route's dynamic classification actually required by real per-request data, or could it be static/ISR with equivalent correctness and lower cost?
      - Is the deployment target (Vercel vs self-hosted) confirmed before citing a platform-specific cache behavior?
      
      If any answer is "not sure," lower the finding's confidence and label the evidence level accordingly — do not present it as a confirmed defect, except for leakage findings with clear file:line evidence of per-user data plus default/cached fetch behavior, which stay HIGH regardless.
      
  • metadata.json 1.6 KB
    {
      "id": "nextjs-rendering-caching-review",
      "name": "Next.js Rendering & Caching Review",
      "type": "skill",
      "provider": "frontend",
      "harnesses": [
        "claude-code",
        "cursor",
        "codex",
        "gemini",
        "kiro",
        "other"
      ],
      "summary": "Reviews Next.js App Router rendering-mode selection and fetch()/Data-Cache configuration for staleness and cross-user data-leakage risk, using Next.js's own caching documentation loaded progressively and grounded via Context7 against the repo's confirmed Next.js version.",
      "source_type": "original",
      "official_docs": [
        "https://nextjs.org/docs/app/building-your-application/caching",
        "https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating",
        "https://nextjs.org/docs/app/guides/incremental-static-regeneration",
        "https://nextjs.org/docs/app/api-reference/functions/revalidateTag"
      ],
      "security_notes": "Cross-user Data Cache leakage (caching a per-user response as if it were shared/public) is a data-exposure defect, not merely a performance nit — this skill escalates such findings to HIGH severity and requires a security-review sign-off, not just a caching-strategy note. Static-review-only skill: it reads and greps route/fetch source but never executes, builds, or runs application code. Treat any hardcoded API key, token, or credential found in a fetch() call or header as a HIGH-severity finding requiring immediate escalation.",
      "last_verified": "2026-07-02",
      "path": "skills/frontend/nextjs-rendering-caching-review",
      "author": "github: VincentChuWaiChow",
      "version": "0.1.0"
    }
    
  • SKILL.md 6.1 KB
    ---
    name: nextjs-rendering-caching-review
    description: Statically review Next.js App Router route segments and fetch() calls for rendering-mode (static/ISR/dynamic) and Data-Cache misconfiguration, escalating cross-user data leakage to a security finding rather than a performance nit.
    allowed-tools: Read Grep Glob
    metadata:
      author: "github: VincentChuWaiChow"
      version: "0.1.0"
      updated: "2026-07-02"
      category: architecture
    ---
    
    # Next.js Rendering & Caching Review
    
    ## Purpose
    
    Review Next.js App Router rendering-mode selection (static / ISR / dynamic) and `fetch()` / Data-Cache configuration without re-litigating component architecture, styling, or Pages Router APIs in every response. This skill exists so caching-staleness and cross-user data-leakage risk stay the focus, and so those adjacent concerns stay out of scope.
    
    ## When to use
    
    Use this skill when the user asks to:
    
    - review caching/revalidation behavior in an App Router PR,
    - investigate a report of stale data being served,
    - investigate a report of one user seeing another user's data,
    - decide whether a route should be static, ISR, or dynamic.
    
    Do not use this skill for:
    
    - Pages Router codebases (`getStaticProps` / `getServerSideProps`) — different API surface; do not apply App Router `fetch()`-cache guidance to it,
    - purely client-side SWR/React Query caching with no server `fetch()` involved,
    - component decomposition or state-placement review — that is `react-component-architecture-review`.
    
    ## Context7 Documentation Protocol
    
    - Resolve `/vercel/next.js` with `resolve-library-id` before citing any caching-default claim.
    - Before asserting a `fetch()` caching default, read the repo's `package.json` to confirm the installed Next.js major version, then call `query-docs` scoped to that version. Next's `fetch()` caching default changed between Next 14 (`cache: 'force-cache'` default) and Next 15 (uncached by default; `GET` Route Handlers also uncached by default). A default claim verified against one major must never be reused for another.
    - If the repo has adopted the `use cache` / Cache Components model (Next 15.x canary / Next 16 opt-in via the top-level `cacheComponents` config, which replaced the removed `experimental.dynamicIO`/`experimental.useCache` flags), treat that as a distinct caching paradigm from the classic `fetch()`-options model — do not mix `cacheLife`/`cacheTag` guidance with classic `next: { revalidate, tags }` guidance in the same finding without confirming which model the route actually uses.
    - `revalidateTag(tag)` accepts a second, version-sensitive `options.profile` argument in newer releases: `profile: "max"` marks the tag stale for background stale-while-revalidate on next visit (the currently recommended pattern); omitting it schedules an immediate expire-on-next-request, which current docs mark as deprecated in favor of `profile: "max"` or `updateTag`. Confirm which signature the installed version supports via `query-docs` before recommending one — do not assume the two-argument form exists on an older major.
    - If Context7 is unavailable, fall back to the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based, unverified against current release`.
    
    ## Lean operating rules
    
    - First read `package.json` to confirm the installed Next.js major version and whether the deployment target is Vercel or self-hosted. Do not assert a caching default or a platform-specific cache primitive (e.g. Vercel Data Cache persistence across deployments) without confirming both.
    - Classify every in-scope route segment as static, ISR, or fully dynamic before evaluating its `fetch()` calls. A route with no `revalidate` export, no `dynamic` export, and no dynamic API (`cookies()`, `headers()`, `searchParams`) usage defaults to static; do not assume dynamic without evidence.
    - Treat cross-user Data Cache leakage — a per-user or session-scoped response cached as if it were shared/public — as a HIGH-severity security finding requiring security-review sign-off, not a caching-strategy suggestion. This is the hard security gate for this skill.
    - Do not recommend `export const dynamic = 'force-dynamic'` on a whole route to fix a leakage finding without first checking whether a scoped `cache: 'no-store'` on the offending `fetch()` call, or a user-scoped cache tag/key, is sufficient. Route-wide `force-dynamic` is a real TTFB/cost overcorrection.
    - Do not conflate Request Memoization (per-render dedup of identical `fetch()` calls, scoped to a single render pass) with the Data Cache (persists across requests/deployments). A finding that treats memoization as if it persisted across users is wrong on its face.
    - Do not treat every dynamic route as a defect. Routes that genuinely require per-request data (auth-gated dashboards, personalized content) are correctly dynamic; only flag dynamic classification when the same correctness could be achieved with static or ISR.
    - Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only).
    - Treat any hardcoded API key, token, session secret, or credential found in a `fetch()` call, header, or example data as a HIGH-severity finding requiring immediate escalation, not a caching note.
    
    ## References
    
    Load these only when needed:
    
    - [Review workflow and findings contract](references/workflow-and-output.md) — use for the step-by-step review procedure, the rendering-mode classification table, the leakage decision tree, and the required output shape.
    - [ISR reference](references/isr-reference.md) — load only for routes using `generateStaticParams` + `revalidate`, or on-demand revalidation via `revalidatePath`/`revalidateTag`.
    - [Cache-tag invalidation reference](references/cache-tag-invalidation.md) — load only when tag-based invalidation (`next: { tags }`, `revalidateTag`) is present in the diff.
    
    ## Response minimum
    
    Return, at minimum:
    
    - per-route rendering-mode table (route, mode, justification),
    - ranked caching findings with file:line, risk class, and fix,
    - the Next.js major version the claims were verified against,
    - verdict: approve / approve-with-notes / block,
    - evidence level and open questions.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related