Får en Next.js-rute til øjeblikkelig navigation under Cache Components/PPR ved at skrive en fejlende Playwright-test og arbejde den grøn, så testen bliver stående som regressionsværn.
@Vibetrends Bot
Community-bidragyder
vercel/next.js
Open source repository
Nedenstående er skillens egen dokumentation, hentet fra kildekoderepoet. Ophavsret tilhører forfatteren.
Set up an agentic optimization loop that drives a Next.js route from "not
instant" to "instant" and keeps it there. The loop is test-driven: encode the
goal as a failing @next/playwright instant() test, work it to green, and
ship the test as the regression guard. Run it once per target route and entry
point. Work the phases P → G in order; each ends in a gate.
Before changing the route, read the bundled Optimizing the static shell
guide at
node_modules/next/dist/docs/01-app/02-guides/optimizing-the-static-shell.md.
If it is unavailable, use the online
guide. The
guide owns the framework behavior and implementation patterns. This skill owns
the navigation contract, production rig, trustworthy RED-to-GREEN loop,
parity check, differential, and report.
One thing here is fixed. The rest is yours. Read this before treating any command, platform, or env var below as a requirement.
@next/playwright instant(). This skill uses
instant()
as a ruler, not a stopwatch (phase A). It comes from
@next/playwright (installed alongside @playwright/test, on the same
release line as next), so it isn't tied to any host. Keep it. Timing a
navigation by hand is too flaky to trust, and is the failure mode this skill
exists to prevent.next build && next start, a CI/staging container, and a per-push preview
deploy are equally valid rigs; the verdict comes from the build, never the
platform. Phase 0 maps the invariant onto your repo. Read every platform
name, env-var spelling, and command below as an example to translate, not a
requirement.A route reaches the user two ways:
loading.tsx).The same implementation patterns can make either entry point instant. The test differs only in how the navigation is driven ("Driving the navigation in tests" below), and the available UI can differ because a soft navigation reuses layouts above the source and destination's divergence point. Guard the entry point the user named, and both when both matter. See What "instant" means.
This skill is therefore not limited to increasing the direct-load static
shell. It can also fix a non-instant client navigation when caching reusable
work or moving request-time work behind a focused boundary makes the
destination's available App Shell commit immediately. When that App Shell is
already instant and the goal is to fetch additional URL-specific content
before the click, hand off to next-partial-prefetching-optimizer.
Make the most meaningful available App Shell UI commit immediately, and let
only genuinely request-time work stream afterward. The shipped test
deterministically encodes present ∧ instant. Non-blank is the additional
bar the workflow enforces by judgment because an instant() pass alone is
satisfied by an empty shell.
instant() is a ruler, not a stopwatch: assert that the shell appears under
the lock; do not time it. A trustworthy verdict requires a production build
(phase A).
The GREEN under the lock is the deterministic verdict; each gate keeps it trustworthy.
This loop is meant to run unattended, so it doesn't stop to ask between steps. Work the navigation the user named, finish it, and stop. What matters is how you word and present the results, not how often you interrupt. The mechanics below — the rig, RED, GREEN, the gates — are your scaffolding; the user never needs to hear those words.
<Suspense> (always fresh, still instant) rather than
guess a cacheLife.- [ ] P PREREQS Next.js 16.3+ with cacheComponents: true; upgrade first → below
- [ ] 0 SETUP once per repo: discover + write instant-nav.rig.md → rig-template.md
- [ ] A RIG production build with the testing API exposed → below
- [ ] B BASELINE unlocked: the marker renders for the test user → test-template.md
- [ ] C RED locked instant(): the shell does not commit → test-template.md
- [ ] C-gate VERIFY-RED: stop until the RED is trustworthy → reference/red-test-robustness.md
- [ ] D FIX apply the matching documented pattern → guide links below
- [ ] D1 reuse existing loading UI; keep the shell meaningful
- [ ] D2 preserve layout and responsive behavior
- [ ] E PARITY the refactor changed only whether the route is instant
- [ ] F DIFFERENTIAL revert only the fix → RED; re-apply → GREEN → reference/red-test-robustness.md
- [ ] G REVIEW PR checklist (below)
Phases B and C build the test; only the locked test from C ships.
The workflow depends on framework capabilities that ship with current Next.js:
cacheComponents: true in next.config.ts. Without
Cache Components there is no static shell to optimize.@next/playwright on the same release line as the project's next; it
provides instant(). Verify with npm ls next @next/playwright (or the
project's package manager) and align them if they differ. The matching
testing API is in the next runtime, gated by the
experimental.exposeTestingApiInProductionBuild
config flag (phase A).If the project does not meet these, upgrade first (npx @next/codemod upgrade
automates most of it), then enable Cache Components in next.config.ts:
export default { cacheComponents: true }
Enabling the flag surfaces the blocking routes to resolve first; the
next-cache-components-adoption
skill drives that adoption. Reach for this optimizer once the app builds under
Cache Components.
This gate is deliberate: the skill targets current Next.js, and none of the verdicts below are meaningful on older versions.
The principles in this skill are fixed; the infrastructure they run on is
yours. On first use in a repository, discover how the project builds, deploys,
authenticates, and tests (inspect the repository first, and ask the user only
what it cannot answer), then write the answers to a committed
instant-nav.rig.md. Every later run reads that file instead of
rediscovering. The required build, test context, navigation contracts,
iteration loop, and file template are in rig-template.md.
If the repo has no Playwright e2e harness yet, standing up a minimal one
(@next/playwright, a config with baseURL, one authenticated path) is part
of this step; the loop does not assume a pre-existing suite.
Stand up the rig described by instant-nav.rig.md. Two invariants hold on
every platform:
Never measure on next dev. It does not prefetch, and its lock is
unreliable for blocking routes, so a dev instant() result is not a valid
RED or GREEN.
The measured build must expose the testing API. Otherwise instant()
silently no-ops and the test passes vacuously (see
reference/red-test-robustness.md). The lock-engagement proof is the phase-C
RED itself: the unfixed target route is the known-blocking route, and its
RED under the lock shows the lock engages on this build (C-gate); the
self-validating variant in test-template.md is the in-band guarantee. Wire
experimental.exposeTestingApiInProductionBuild to a condition that is true
for every build you measure and never true in production:
experimental: {
// Use the condition your platform provides, and record it in the rig file:
// local: an explicit opt-in, as below
// generic CI: process.env.DEPLOY_ENV === 'staging'
// Vercel: process.env.VERCEL_ENV === 'preview'
exposeTestingApiInProductionBuild:
process.env.EXPOSE_TESTING_API === '1',
}
The rig is any production-like build that exposes the testing API: a local
next build && next start, a CI/staging container, and a preview deploy are
all equally valid; the verdict comes from the build, not the platform. See
rig-template.md for the setup requirements.
For any deployed or remote build, poll the rig's LIVENESS probe to confirm the
artifact contains HEAD before trusting a verdict (a stale deploy reads as a
false RED or GREEN); a local next build && next start needs none. The probe
mechanism is in rig-template.md.
Drive the real navigation with no instant() lock and assert that the
destination's SHELL_MARKER renders as the test user: the account the
e2e suite authenticates as (in CI, the CI account; locally, your e2e login
fixture), with its flags, plan, role, and data. This establishes that the
marker is real and reachable: not flag-gated, not redirected away, not a
guessed selector. The suite runs as the test account, not the author's session;
that environment drift (the rig DRIFT list) is a common source of
untrustworthy REDs. Scaffold and run command: test-template.md.
Delete this baseline before the PR.
Wrap the same navigation in instant(); assert the shell commits under the
lock. A RED here is the gap. This is the test that ships
(test-template.md).
Assert the complete intended contract: meaningful shell markers and existing loading states are visible, request-time UI is absent under the lock, and the completed UI renders after release. Do not change an existing data source, production selector, or required route variant to make the test easier. A required contract may not be skipped or weakened.
Prefer the self-validating variant when the route has deferred content. If the
route cannot build while blocked, or a cookie/session read stays GREEN, use the
RED recipes in reference/red-test-robustness.md.
C-gate: do not start optimizing until the RED is verified trustworthy. A RED that is red for the wrong reason sends you optimizing a route that was never broken.
The question that settles it: does SHELL_MARKER render without the lock,
as the test user? Answer it by re-running phase B as the test user, not by
adding assertions to the shipped test. The two-branch resolution (No → marker
or environment bug; Yes → genuine gap, proceed to D), the full taxonomy of
untrustworthy REDs, the checklist, and worked cases are in
reference/red-test-robustness.md. Read it now.
Use the Optimizing the static shell guide you read at the start. Follow the section that matches the blocker instead of duplicating its framework guidance here:
Dokumentationen er forkortet. Læs den fulde version på GitHub.