iru-nextjs-code-one-task
Implement a single task from a Next.js `implementation_plan.md`-style task list — the implementation, its tests, and its checkbox update. First detects the project's exact profile at runtime — the ins
SKILL.md.claude/skills/iru-nextjs-code-one-task/SKILL.md
GitHubImplement one plan task (Next.js)
Carry out a single task's implementation, tests, and checkbox update in a Next.js project — nothing else. This is
the narrowest execution unit for a nextjs-tagged task in the iru-code pipeline: iru-nextjs-code-one-task-group
calls it once per task in a bucket (possibly many at once, in parallel agents), then runs license headers, TSDoc,
tests, coverage, code quality, next build, and Playwright once for the whole bucket.
What makes it different from iru-typescript-code-one-task, and why Step 1 is longer:
- The same code means different things in different Next.js versions and caching models.
fetchis cached by default in 14 and not in 15+;paramsis a plain object in 14 and a Promise from 15;export const revalidate = 60is ISR in the previous model and a build error under Cache Components. Step 1 pins the profile so that only one set of rules is applied. - The route's rendering mode is part of the contract. A stray
cookies()at the top of a layout turns every static route below it dynamic and nothing fails until someone reads the build table. This skill states the mode it expects for each route it touched; the group skill checks it againstnext build.
It uses a medium model on purpose: the plan carries the hard reasoning; the Next.js rules live in reference/.
Step 1 — Detect the project profile, then read only the reference material this task needs
Detect each of these once, from the project root (the directory holding next.config.*; in a monorepo, the app
package the task names). Don't guess any of them from memory.
- Next.js version:
node -p "require('next/package.json').version"(falls back to thenextrange inpackage.jsonwhennode_modulesis absent — say so in Step 7). Also note thereactversion. - Router:
app/orsrc/app/→ App Router;pages/orsrc/pages/→ Pages Router; both → hybrid (each file follows the router its directory belongs to). A rootapp/wins oversrc/app/. - Caching model (App Router on 16): read
next.config.{ts,mjs,js}forcacheComponents: true. Never infer it from the scaffold's age — acreate-next-app16.4 run may emit it enabled together withpartialPrefetching: true. Also noteoutput: "export"/"standalone",typedRoutes,reactCompiler, and anyexperimentalkeys the task's area depends on (authInterrupts,serverActions,taint). - Profile:
14(13.4–14.x),15(15.x; note whether it is ≥15.5 forPageProps/RouteContext/next typegen),16-prev(16 withoutcacheComponents),16-cc(16 with it). Pages-Router-only code has no caching profile beyond the version. A 15 canary withexperimental.ppr/dynamicIOis15plus a flag — report it rather than applying16-ccrules. - Request interception:
proxy.ts(16, exportproxy) ormiddleware.ts(14/15, exportmiddleware; on 16 a leftover kept for the Edge runtime), next toapp/. - Test runners:
vitest.config.*→ Vitest;jest.config.*importingnext/jest→ Jest;playwright.config.*@playwright/test→ Playwright e2e (note itstestDir). Also note where existing tests live.
- Styling and lint: Tailwind (v3 config file vs v4
@import "tailwindcss"), CSS Modules, Sass, CSS-in-JS;eslint.config.mjs(flat,eslint-config-next), legacy.eslintrc(≤15), orbiome.json. - Package manager:
packageManagerfield or lockfile. - Agent guidance: read the project's
AGENTS.md/CLAUDE.mdif present. When it carries thecreate-next-appblock pointing atnode_modules/next/dist/docs/, those docs match the installed version: for the specific API the task uses,grep -rilthat directory and read the one relevant page — not the whole tree.
Then read reference/README.md first — it is short and routes you to the rest — and only what it points at for this
task. The routing, restated:
| Read | When |
|---|---|
reference/code-style.md |
Always. |
reference/server-and-client-components.md |
App Router, always. |
reference/data-fetching-and-caching.md |
Data reads, caching, revalidation, anything affecting static/dynamic. Read the shared rules + only the detected profile's section. |
reference/server-actions.md |
Server Actions, forms posting to them, useActionState/useFormStatus/useOptimistic. |
reference/route-handlers.md |
route.ts, webhooks, BFF endpoints, proxy.ts/middleware.ts. |
reference/routing-and-rendering.md |
New routes/segments/layouts/special files, dynamic segments, generateStaticParams, segment config, navigation, output: "export" projects. |
reference/metadata-and-seo.md |
metadata/generateMetadata/viewport, OG images, sitemap/robots, JSON-LD. |
reference/pages-router.md |
The touched code lives under pages/ — replaces the Server/Client Components file for those files. |
reference/testing.md |
The task writes or updates tests — nearly always. |
reference/security.md |
Actions, handlers, proxy, sessions/auth, env vars, server data passed to Client Components, headers/CSP, user input. |
reference/ui-contract.md + reference/ui-implementation.md |
The task adds or changes a page, layout, component, list or other UI. reference/ui-screenshots.ts is the shared Playwright helper to copy into the e2e directory's support/ when the project has none. |
A one-line change inside an existing Client Component is code-style.md + server-and-client-components.md +
testing.md. Don't read the rest speculatively.
If reference/ is missing (the skill directory was copied without it), proceed with this repository's own
CLAUDE.md/AGENTS.md conventions and the surrounding code's style, and say so in Step 7 — don't guess at what the
reference would have said.
Step 2 — Re-check the current code state
Read the actual current content of the file(s) the task touches before editing — other tasks in the same bucket may be landing concurrently, and "current code state" notes passed in may be stale. For every route the task touches, also read the layouts above it and the data modules it imports: whether a route is static or dynamic is decided by the whole tree, not the file you edit.
Note what the surrounding code already does about the things reference/ has opinions on — where data access
lives, how actions return errors, how tests mock the session, which styling approach is used. The reference is the
default for new code; a file that consistently does something else wins, and the divergence goes in Step 7.
Step 3 — Implement exactly what the task specifies
Apply the reference files Step 1 selected, for the detected profile only, plus this repository's own
CLAUDE.md/AGENTS.md conventions where they are more specific. Non-negotiable, because a later gate enforces
them or because getting them wrong is a security defect:
- Profile-correct APIs.
awaitrequest APIs andparamson 15+ (never on 14); two-argumentrevalidateTag,updateTag,refreshonly on 16;'use cache'/cacheLife/cacheTagonly under16-cc; route segment configdynamic/revalidate/fetchCache/dynamicParamsnever under16-cc;proxy.tsvsmiddleware.tsas detected. Nothing fromoutput: "export"'s forbidden list in an export project. - Every Server Action and Route Handler authenticates, validates, and authorizes inside itself — no reliance on
a layout, the proxy, or a hidden button. Data modules start with
import "server-only"(App Router only). - No secrets or whole rows cross to the client — DTOs as props,
NEXT_PUBLIC_only for genuinely public values. 'use client'on the smallest interactive leaf, never on a page or layout unless the task requires it.- Don't change a route's rendering mode the task didn't ask to change. Under
16-cc, request data and uncached I/O go inside<Suspense>; never addexport const instant = falseto make the build pass unless the task says the route may block. - Full TSDoc on every exported symbol (
@param/@returns/@throws); a one-line summary for default-exportedpage/layoutcomponents. - No
any,readonlyby default, named exports except where Next.js requires a default export. - No new runtime dependency and no
next.configchange unless the task explicitly calls for it — otherwise that's a blocker to report (Step 5). Never flipcacheComponents, never upgrade Next.
For a task that adds or changes UI, also decompose first (ui-contract.md §2): write the Screen › Region › Component tree for what the task touches — inputs, events, visual states, reuse, and for each component whether it
is a Server or a Client Component — and keep it for Step 7. Pages own data and pass DTO props down; interactive
leaves are Client Components emitting typed callbacks; collections are server-paginated or client-virtualized
(ui-implementation.md), never an unbounded map; styles use logical properties.
Don't add anything the task didn't ask for — no speculative abstractions, no unrelated cleanup, no restyling.
Step 4 — Write or update the tests
Follow reference/testing.md and the existing tests next to the code. Client Components and synchronous Server
Components with React Testing Library; leaf async Server Components by calling them as functions; Server Actions and
Route Handlers called directly with the session, next/headers, and next/cache mocked — always including the
anonymous, wrong-owner, and invalid-input cases, asserting no side effects. Add or extend a Playwright spec only
for a user-visible flow the task adds (a form submit, a redirect, an auth gate) and only if the project already has
Playwright. Keep async Server Components thin so their logic is unit-testable — a page with branching logic and no
unit test is a coverage gap the group skill will send back.
For UI, also write UI tests per ui-contract.md §6–§8 and ui-implementation.md, if the project has Playwright:
stories for each visual state of every added/changed Client Component (rendered by the gallery), a screen spec for
a changed page against the production build with upstreams faked, and a flow spec for a new flow — all capturing
the .ui-quality.yml matrix through the project's ui-screenshots.ts helper (created from
reference/ui-screenshots.ts if missing, with @axe-core/playwright as the one permitted new dev dependency) with
the accessibility check. A project without Playwright: behaviour tests only, and report the gap.
Do not run the tests, the type-checker, or next build — iru-nextjs-code-one-task-group does, once for the whole
bucket.
Step 5 — When to interrupt the user
Keep interruptions rare. Stop and use AskUserQuestion (or plain text if there's no real choice) only when:
- The task is ambiguous in a way that changes correctness, scope, or a route's rendering mode and can't be safely inferred (e.g. it asks for per-user content on a page the plan elsewhere expects to stay static).
- The task's approach is infeasible against the real code — it names a route, action, or module that doesn't exist,
or asks for something the detected profile forbids (a Server Action in an
output: "export"project,export const revalidateunder16-cc). Name the profile rule; don't silently translate it into another API unless the translation is mechanical and documented inreference/(then do it and report it). - It needs a new dependency, a
next.configchange, or a new environment variable the task didn't mention. - The profile detection in Step 1 is genuinely ambiguous (no
node_modulesand anextrange spanning two majors).
Don't stop merely because the task is nontrivial. Don't report a task done to work around a blocker — report it
blocked (Step 7) with what was tried and why it failed. A conflict between reference/ and the surrounding code is
not a reason to stop: follow the surrounding code and report the divergence.
Step 6 — Check off this task's checkbox now
Unless this task stopped on a blocker, update implementation_plan.md at the repository root before reporting.
Re-read it fresh (sibling tasks may be editing it concurrently), find this task's own checkbox line by the exact
text passed in, flip [ ] to [x] for it and for every sub-task completed, and add a short note, e.g. - [x] Task 2. **Add addTodo Server Action** — implemented, tests added; group validation pending. — the same "pending"
wording iru-nextjs-code-one-task-group replaces once it has validated the bucket. Edit only this task's own
line(s). If the task stopped on a blocker, leave its checkbox untouched.
Step 7 — Report the outcome
Hand control back with a short summary:
- The detected profile — Next version, router(s),
14/15/16-prev/16-cc,src/or not, proxy vs middleware,outputmode, unit runner and whether Playwright exists — and the reference files actually read (plus any bundlednode_modules/next/dist/docspage consulted). - The files touched and the tests added/updated (not run).
- For UI work: the
Screen › Region › Componentdecomposition (with Server/Client per component), the stories and UI specs added, and the screenshot states they will capture (<kind>/<Id>/<state>). - Expected route classification for every route the task added or whose tree it touched, in
next buildterms — e.g./todos ƒ (reads the session cookie),/posts/[slug] ● (generateStaticParams),/shop ◐ (cookie read under Suspense),/api/todos/[id] ƒ,/ unchanged ○— and whether any of them changed mode, and why. Name the files that are only reachable through e2e (thin async pages/layouts) so the coverage gate can account for them. - Whether this task stopped on a blocker (Step 5), with enough detail for the caller to surface it.
- Any deliberate deviation from
reference/— matching a file that uses another convention, a profile rule translated into its equivalent, a default export Next.js requires — or thatreference/was missing.
This skill never captures a quality baseline and never runs tests, coverage, linting, type-checking, or
next build.