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.
Install
npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/nextjs-rendering-caching-review
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
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 Routerfetch()-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.jswithresolve-library-idbefore citing any caching-default claim. - Before asserting a
fetch()caching default, read the repo'spackage.jsonto confirm the installed Next.js major version, then callquery-docsscoped to that version. Next'sfetch()caching default changed between Next 14 (cache: 'force-cache'default) and Next 15 (uncached by default;GETRoute 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-levelcacheComponentsconfig, which replaced the removedexperimental.dynamicIO/experimental.useCacheflags), treat that as a distinct caching paradigm from the classicfetch()-options model — do not mixcacheLife/cacheTagguidance with classicnext: { revalidate, tags }guidance in the same finding without confirming which model the route actually uses. revalidateTag(tag)accepts a second, version-sensitiveoptions.profileargument 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 ofprofile: "max"orupdateTag. Confirm which signature the installed version supports viaquery-docsbefore recommending one — do not assume the two-argument form exists on an older major.- If Context7 is unavailable, fall back to the
official_docsURLs in this skill'smetadata.jsonand label the claimdocumentation-based, unverified against current release.
Lean operating rules
- First read
package.jsonto 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 norevalidateexport, nodynamicexport, 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 scopedcache: 'no-store'on the offendingfetch()call, or a user-scoped cache tag/key, is sufficient. Route-wideforce-dynamicis 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 viarevalidatePath/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.
Reviews (0)
No reviews yet.
No comments yet.