next.js
c6c1f75e - Replace next-ppr-optimizer with next-cache-components-optimizer (#94035)

Commit
2 days ago
Replace next-ppr-optimizer with next-cache-components-optimizer (#94035) ## Summary Replaces `next-ppr-optimizer` (shipped in #93943) with a merged `next-cache-components-optimizer` skill that covers both page-render and in-app-nav optimization. The two diagnostics share a preflight, lever set, anti-pattern list, and reference table; the gating requirement (`cacheComponents: true`) is what unifies them, hence the rename. - `SKILL.md` — top-level router; shared preflight, shared `instant cookie` section (value tuple + set command + p-prefix convention — both loops reference this rather than duplicating it), refactor levers, plan-mode gate, no-shell bailout, visible-delta verify (with required screenshots and dev-overlay-hidden capture), anti-patterns, gotchas (including a concrete DOM-settle poll and a flakiness re-run rule), reference table with shapes for the remaining primitives (`agent-browser react suspense --only-dynamic --json` boundary schema + raw blocker names, `__nextjs_original-stack-frames` request body, `mcp get_logs` origin, `cacheLife` profiles), teardown. - `ppr-loop.md` — page-render diagnose loop. Same `react suspense --only-dynamic --json` primitive as the nav loop (consistency); pixel-area ranking on shell-only render; gauge step that skips marginal refactors. - `instant-nav-loop.md` — nav diagnose loop. Single capture on B post-`pushstate` via `react suspense --only-dynamic --json`; each boundary's `suspended_by[].name` is classified into client-hook / request-api / server-fetch / cache. **Client-hook blockers (`usePathname`, `useSearchParams`, `useRouter`, …) are dropped** — they suspend only during SSR prerender and resolve instantly on SPA nav, so "fixing" them is wasted work. Shared layouts (the path A and B have in common) are filtered out implicitly — React keeps them mounted across the navigation, so their boundaries don't re-suspend and never enter the capture. With multiple real candidates, rank by where the boundary sits in B's new-segments tree; fix the one closest to where A's and B's paths diverge first. Plain-language framing throughout (no LCA/graph-theory jargon). Includes a gauge step that skips marginal refactors. Plus the nav-only **third lever**: `'use cache: private'` for I/O reading `cookies()` / `headers()` / `searchParams`, paired with `unstable_prefetch = 'force-runtime'` on a route segment that owns the private content. The framework resolves private-cached content at link-prefetch time so the click commits with cookie-derived data already in place. ## Why - **Dedup.** Both loops share the same refactor levers (push-down + cache), `cacheLife` profile list, instant-cookie protocol, null-fallback anti-pattern, no-shell bailout, DOM-settle polling pattern, and teardown — no duplication across files. - **Discoverability.** \`cache-components\` matches the canonical config flag developers search for. ## Workflow guarantees - **Manual user setup precondition.** The skill doesn't drive auth/SSO/MFA. The user opens the headed browser (via `next-dev-loop`), logs in if needed, and navigates to the starting page; only then does the skill take over. Spelled out in both shared and per-loop preflights. - **Plan mode for every refactor.** Agent presents a plan (candidate, lever, file path, expected delta, freshness preset for cache) before applying. User can iterate before agreeing. - **Visible-delta verify.** A baseline screenshot is taken before any change; an after-screenshot is taken in verify. Both paths are reported in the final summary. Identical-looking captures = refactor didn't land → undo. Nav loop captures via cookie-locked SPA-nav (be on A → set cookie → `pushstate <B>` → settle → screenshot); page-render loop captures via shell-only render. - **Dev overlay hidden during capture.** `nextjs-portal` (instant-nav guidance, error overlay, route indicators) is hidden via JS, screenshot taken, restored — so the before/after isn't polluted by dev chrome. - **No-shell bailout.** If the route is fully blocking (HTTP 500 with `blocking-route` / `NEXT_STATIC_GEN_BAILOUT`, or zero Suspense boundaries on a visibly-rendered page), the optimizer stops; the user fixes the structural wrap first. - **Gauge before applying.** Each loop's diagnose includes a gauge step using the same capture as verify (shell-only render for PPR, cookie-locked SPA-nav for nav). If the top-ranked candidate is sub-viewport, the optimizer offers to audit other routes / nav pairs instead of forcing a low-ROI refactor. ## Validation Headless evals against a `cacheComponents` demo app via `claude -p`: | Scenario | Outcome | |---|---| | Already-optimal page (no-fix branch) | Declined refactor correctly | | Unwrapped page-level \`await\` (push-down) | Extracted I/O into Suspense-wrapped child, autonomous | | Uncached real I/O (cache + collab gate) | Asked for \`cacheLife\`, applied to helper | | Multi-candidate w/ recursion trap | Ranked by LCA-most-first, recursed into wrapper rather than blind-wrap | | PPR-flavored prompt → ppr-loop | Decision step picked the right sub-loop; sub-reference loaded correctly | | Nav-flavored prompt → instant-nav-loop (polished) | Decision routing + terser prose preserved behavior end-to-end | ## Notes - Removes `skills/next-ppr-optimizer/` shipped in #93943. - A drafted `next-instant-nav-optimizer` was built during development but never committed; not part of this diff. ## Test plan - [ ] Manual: invoke \`/next-cache-components-optimizer\` against a \`cacheComponents\` app; confirm the agent picks the right sub-loop based on the request phrasing. - [ ] Manual: confirm the preflight inherits from \`next-dev-loop\` correctly (user-driven browser session must be open first). <!-- NEXT_JS_LLM_PR -->
Author
Parents
Loading