Install
openclaw skills install @dennisrongo/nextjs-app-routerScaffold a new Next.js (App Router) fullstack app with TypeScript, NextAuth (Auth.js v5), Prisma + PostgreSQL, Route Handlers, Redux Toolkit + RTK Query, Tailwind + shadcn/ui, React Hook Form + Zod. Pages are 'use client' SPA-style — RTK Query talks to in-app /api/** Route Handlers; no fetch() in server components, no Server Actions. Three modes: full project scaffold, add a feature slice, add an RTK Query API slice. Use this skill whenever the user says "create a new Next.js project", "scaffold a Next.js app", "new Next app with auth", "Next.js + NextAuth", "Next.js + Prisma project", "RTK Query Next.js app", "shadcn project", "my Next.js conventions", or "/nextjs-app-router" — even if they don't name the skill. Full good-pattern catalog and pitfall list live in the skill body.
openclaw skills install @dennisrongo/nextjs-app-routerGenerate a production-grade Next.js fullstack app where:
'use client' SPA-style. All data goes through RTK Query. No fetch() in server components. No Server Actions. No async page.tsx. The root app/layout.tsx is the only server component that matters — it renders <Providers>. Per-feature page.tsx files are client components.src/app/api/**/route.ts. Each handler calls await auth() and queries Postgres via Prisma.src/auth.ts is the single source of auth truth. middleware.ts re-exports auth for route-level gating. Login UIs call signIn() from next-auth/react; logout calls signOut(). RTK Query reads the session cookie automatically via credentials: 'include'.The skill forbids the patterns it used to allow in an earlier frontend-only revision: server-side data fetching, redirect() from server pages, custom httpOnly JWT cookies, multi-createApi setups, and ad-hoc auth slices that duplicate NextAuth state.
Trigger on any of:
<domain>"If unsure whether the user wants a brand-new project vs. an addition to an existing one, ask once — don't guess.
Pick the mode from the user's request. If ambiguous, ask.
| Mode | Trigger | Output |
|---|---|---|
scaffold-project | "new project", "scaffold app", empty directory | Full Next.js App Router project: route groups, server root layout, client pages, NextAuth config, Prisma schema + migration, /api/auth/[...nextauth] handler, base RTK Query pointing at /api, shadcn/ui init, Vitest + Playwright, ESLint/Prettier/Husky, GitHub Actions CI. |
add-feature | "add <feature> end-to-end", "wire up <screen> through routing + API + state + form" | New route folder under the appropriate group with a 'use client' page.tsx, _components/, _hooks/, loading.tsx, Zod schema, RTK Query endpoint file, and a matching src/app/api/<feature>/route.ts Route Handler that calls auth() and Prisma. |
add-api-slice | "add an API slice for <domain>", "new RTK Query endpoints for X" | New src/redux/api/<domain>Api.ts injecting endpoints into the base api, plus matching src/app/api/<domain>/**/route.ts handlers (one per endpoint) and Prisma model additions if needed. |
Versions are resolved at scaffold time, never hard-pasted into the skill. Before writing package.json:
Check the user's environment first: run node --version and npm --version (or pnpm --version / yarn --version).
Resolve the latest stable versions of the core stack via context7 (mcp__plugin_context7_context7__query-docs) — never hand-paste. Resolve:
next, react, react-dom, typescript, @types/react, @types/nodenext-auth (Auth.js v5 — current beta as of late 2025; verify via context7), @auth/prisma-adapterprisma, @prisma/client, bcryptjs (and @types/bcryptjs) — or @node-rs/argon2 if user prefers Argon2@reduxjs/toolkit, react-reduxtailwindcss, postcss, autoprefixer@radix-ui/* (only the primitives the templates need), class-variance-authority, clsx, tailwind-merge, tailwindcss-animate, lucide-reactreact-hook-form, @hookform/resolvers, zoddate-fns (do not also add moment)vitest, @testing-library/react, @testing-library/jest-dom, jsdom, @playwright/testeslint, eslint-config-next, @typescript-eslint/*, eslint-plugin-react-hooks, eslint-plugin-jsx-a11y, prettier, husky, lint-stagedQuote the resolved versions back to the user before generating, so they can object.
Prefer the latest stable Next.js (App Router GA from 13.4; resolve current).
Default to pnpm if pnpm is present, otherwise npm. Match packageManager in package.json.
Concrete resolution command when context7 is unavailable: npm view <pkg> version (e.g. npm view next version; npm view next-auth dist-tags — plain npm view next-auth version returns the latest stable, which may still be v4; never scaffold v4). Never write a version you did not just resolve this session — no versions from memory, no latest/^latest, no invented numbers.
API-drift guard. The version you resolved in this step decides the API shape — training-data memory is the least trustworthy source in this workflow. Highest-risk hallucination zones:
authOptions export, getServerSession, NEXTAUTH_* env vars, an options object in the [...nextauth] route. v5 is const { handlers, auth, signIn, signOut } = NextAuth(config) in src/auth.ts, with AUTH_* env vars.params/searchParams are Promises in dynamic route segments and Route Handler contexts; check the resolved major before writing params.id. In 'use client' pages read route params with useParams() from next/navigation, not the params prop.fetchBaseQuery options, providesTags/invalidatesTags shapes) and the Prisma client API for the resolved major.tailwind.config.ts-first setup for CSS-first config (@import "tailwindcss" + @tailwindcss/postcss); v3 uses tailwind.config.ts + postcss.config.js. Emit exactly one style — the one matching the resolved major — never a mix.z.email(), reworked error customization). A v3-only signature against a resolved v4 is a hallucination — check the installed types under node_modules/zod.generator block shape and default client output location changed across majors. After prisma generate, confirm the actual import path from what was generated — don't assume @prisma/client from memory.npx shadcn@latest (init / add); the old shadcn-ui package name is dead.If you are not certain a symbol exists in the resolved version, verify it (read the installed types under node_modules/<pkg>/, or check docs) before writing code that depends on it.
For scaffold-project, use AskUserQuestion to collect:
package.json name).docker-compose.yml with a postgres:16 service), Neon, Supabase, Railway, "I'll paste my own DATABASE_URL". Confirm before writing .env.example and prisma/schema.prisma.User table with a bcrypt-hashed password column), GitHub OAuth, Google OAuth, "all of the above". Default: Credentials. The chosen providers determine which .env.example keys get emitted (AUTH_GITHUB_ID / AUTH_GITHUB_SECRET, etc.).(public) for auth pages + (app) for authenticated content. Optionally add (admin) for role-gated routes (gated via NextAuth role claim in middleware.ts).dashboard) — if provided, run add-feature for it after scaffold.For add-feature: feature name, route group it belongs to, list of fields (name + Zod type + required/optional), CRUD shape (list view + detail view, or just one screen). The skill generates both the client page/components and the matching Route Handler(s).
For add-api-slice: domain name (e.g. customers), list of endpoints (verb + path + request type + response type), invalidation tags. The skill generates the RTK Query slice and the Route Handlers it calls.
Use the templates in references/templates/ as the source of truth. Apply these rules:
Write for new files. Never Edit files you're creating fresh.{{ProjectName}}, {{Feature}}, {{Domain}} placeholders consistently. {{project-name}} is kebab-case for files/dirs; PascalCase where namespacing requires.New-Item -ItemType Directory -Force.create-next-app to bootstrap — write files directly from templates so the layout matches references/folder-layout.md. Use npm/pnpm install after package.json is written.components.json from references/templates/components-json.md and src/components/ui/ components incrementally as needed (button, input, form, label, dialog, toast, etc.) — don't blanket-install every Radix primitive.shadcn CLI, prisma init, or create-next-app at the user's insistence) and its output differs from this skill's layout: the framework wins on file locations it mandates (middleware.ts placement, app/ conventions, prisma/schema.prisma), this skill wins on everything the framework doesn't mandate (redux layout, route groups, _components/_hooks). Reconcile deliberately and list every deviation in your report — never force the skill layout over a framework requirement, never silently abandon the skill's patterns.After package.json and prisma/schema.prisma are written:
pnpm install (or chosen PM) — installs prisma and @prisma/client.DATABASE_URL is reachable before running any DB commands. If they picked local Docker, run docker compose up -d postgres first.pnpm exec prisma generate — generates the typed client.pnpm exec prisma migrate dev --name init — creates the initial migration covering NextAuth tables + sample domain models.
prisma db push against a production-shaped database. Use migrations.prisma migrate reset without explicit user confirmation — it drops the DB.AUTH_SECRET for the user: pnpm exec auth secret (Auth.js CLI) or openssl rand -base64 32. Put it in .env.local (NOT .env.example).prisma generate MUST run before pnpm typecheck / pnpm build — the @prisma/client types don't exist until generated. If typecheck explodes with missing Prisma types, you skipped or mis-ordered this step; run generate, don't start rewriting imports.pnpm typecheck (or npx tsc --noEmit). Must exit 0.pnpm lint. Must exit 0.pnpm test (Vitest) if any tests were generated. Must exit 0.pnpm build. Must succeed.✓ Compiled successfully, Vitest's Tests N passed). Never report success from memory of the steps you intended, and never report partial success as success — if something is red, say exactly what is red.pnpm install, prisma migrate, pnpm build, etc.): read the full error output, change exactly one thing, retry once. If the same step fails twice, stop scaffolding and surface the verbatim error to the user — do not keep generating files on a broken base, and do not re-run the identical command hoping for a different result.AUTH_SECRET and DATABASE_URL), next steps (pnpm dev, log in with the seeded test user if Credentials was chosen).See references/folder-layout.md for the full tree, file-by-file purpose, and the rules that govern it.
Top-level shape:
{{project-name}}/
package.json
tsconfig.json # strict: true, paths: { "@/*": ["./src/*"] }
next.config.ts
tailwind.config.ts # darkMode: ['class'], shadcn theme tokens
postcss.config.js
components.json # shadcn config; rsc: true
.eslintrc.json (or eslint.config.mjs)
.prettierrc
.env.example # NEVER .env — only .env.example committed
middleware.ts # NextAuth-driven route gate; re-exports `auth`
docker-compose.yml # optional: only if user picked "local Docker"
prisma/
schema.prisma # NextAuth tables + domain models
migrations/ # generated; checked in
seed.ts # optional: seeds test user for Credentials
src/
auth.ts # NextAuth (Auth.js v5) config — adapter, providers, callbacks
auth.config.ts # Edge-safe config (no DB/adapter imports) — used by middleware
app/
layout.tsx # SERVER. <html><body><Providers>{children}
globals.css
error.tsx
not-found.tsx
api/
auth/[...nextauth]/route.ts # exports { GET, POST } from NextAuth handlers
<domain>/route.ts # list + create
<domain>/[id]/route.ts # get + update + delete
(public)/ # unauthenticated routes (login, signup, reset)
layout.tsx # server; minimal chrome
auth/login/
page.tsx # 'use client' — calls signIn('credentials', { ... })
(app)/ # authenticated routes
layout.tsx # server; renders <AppShell> ('use client' child)
_components/AppShell.tsx
loading.tsx
error.tsx
dashboard/
page.tsx # 'use client' — uses RTK Query hooks
(admin)/ # optional; role-gated
components/
ui/ # shadcn primitives (button, input, form, ...)
forms/ # composed form fields
Notifications.tsx
UnsavedChangesWarning.tsx
redux/
store.ts # typed; NO @ts-ignore, NO serializableCheck: false
providers.tsx # 'use client'; wraps <SessionProvider><Provider><Toaster>
hooks.ts # typed useAppDispatch, useAppSelector
api/
api.ts # base createApi pointing at '/api'; auth-aware baseQuery
tags.ts # tag-type union as const
<domain>Api.ts # one file per domain; injectEndpoints
lib/
db.ts # PrismaClient singleton
utils.ts # cn() — clsx + tailwind-merge
zod-utils.ts # getDefaultValuesFromSchema, unwrapZodEffects, getMaxLengthsFromSchema
api-auth.ts # requireSession() helper for Route Handlers
formatters/
types/
next-auth.d.ts # module augmentation for Session.user.id, role
config/
env.ts # runtime-validated env (Zod parsed at startup)
tests/
unit/ # Vitest + RTL
e2e/ # Playwright
.github/workflows/ci.yml # lint + typecheck + test + build
Dependency rules (enforced):
app/ routes import from components/, redux/, lib/, config/ — never the reverse.app/api/**/route.ts files import @/auth, @/lib/db, @/lib/api-auth, and Zod schemas from feature folders — never from redux/.redux/ never imports from app/api/** (handlers) or prisma/ — RTK Query talks to handlers over HTTP, not through in-process function calls.components/ui/ (shadcn primitives) must not import from app/, redux/, or feature code._components/ and _hooks/ are private: only routes inside the same feature folder may import them.Full templates are in references/templates/. Rationale for each Keep/Eliminate rule is in references/good-patterns.md and references/anti-patterns.md.
src/app/layout.tsx is a server component that renders <html><body><Providers>{children}</Providers></body></html>. Every other page.tsx is 'use client' and uses RTK Query for data. Per-route metadata lives at the layout level (server) — pages can't export metadata because they're client. See references/templates/root-layout.md.(public), (app), optional (admin). Each group has its own layout.tsx. The shell (header/sidebar) lives in (app)/layout.tsx rendering an 'use client' child from _components/.src/auth.ts exports { handlers, auth, signIn, signOut }. middleware.ts re-exports auth (or wraps it for role/path logic) with an Edge-safe src/auth.config.ts. See references/templates/nextauth-config.md./api/<domain>/route.ts calls await auth() first; unauthenticated requests 401. Inputs validated with the same Zod schema the form uses. Database access via the Prisma singleton in src/lib/db.ts. See references/templates/route-handler.md.api in redux/api/api.ts with baseUrl: '/api', credentials: 'include', and endpoints: () => ({}). Domain slices call api.injectEndpoints(...). Tag types are a const array. See references/templates/api-base.md and references/templates/api-slice.md.baseQuery: on 401, dispatches NextAuth signOut({ callbackUrl: '/auth/login' }). No hard window.location.href = .... The session cookie is sent automatically because credentials: 'include' is set and the API is same-origin.useAppDispatch/useAppSelector from redux/hooks.ts. Components never use the raw useDispatch/useSelector.serializableCheck left at the default. Targeted exceptions with comments are fine; blanket false is not. No @ts-ignore.src/lib/db.ts to avoid exhausting connections during dev hot-reload. See references/templates/db-client.md.User, Account, Session, VerificationToken) plus a password column on User for Credentials. Domain models live in the same file. See references/templates/prisma-schema.md.components/ui/ (owned by the project, not a node_modules dependency). Composed form fields in components/forms/. cn() util in lib/utils.ts is the single source of class merging.useForm({ resolver: zodResolver(schema), defaultValues: getDefaultValuesFromSchema(unwrapZodEffects(schema)) }). The same Zod schema validates request bodies in the matching Route Handler. See references/templates/form-with-zod.md.UnsavedChangesWarning wired to React Hook Form's formState.isDirty._components/ and _hooks/ private to that route.error.tsx and loading.tsx at every meaningful route segment.src/config/env.ts using Zod. AUTH_SECRET, DATABASE_URL, AUTH_URL (production), and any OAuth provider keys are required.date-fns. No moment.// TODO without an issue link. One short line max — no multi-line comment blocks, no multi-paragraph JSDoc. Well-named identifiers carry the what; comments earn their place only when they carry why.tsconfig.json strict + @/* path alias — see references/templates/tsconfig.md.prettier --write + eslint --fix on staged files.Every one of these is forbidden in generated code. Rationale in references/anti-patterns.md.
fetch() or await db.* inside a server component (server page.tsx, layout.tsx, or any non-'use client' file under app/ that isn't route.ts). All data goes through RTK Query → Route Handlers. The app is "API-driven, not SSR" by deliberate choice.'use server' functions called from client components). The skill does not emit any. Mutations go through Route Handlers + RTK Query mutations.async page.tsx — pages are 'use client' and synchronous. The body uses RTK Query hooks.redirect() from a server page.tsx as the auth-gate fallback. The auth gate is middleware.ts. The root / redirect is also middleware's job (e.g. send signed-in users to /app/dashboard, signed-out to /auth/login).httpOnly-cookie schemes alongside NextAuth. Pick one. The default is NextAuth — don't generate a parallel setCookie('session', ...) flow in a Route Handler.useSession() from next-auth/react (or call await auth() server-side in Route Handlers).serializableCheck: false on the store config without targeted ignoredPaths/ignoredActions and a comment explaining the exception.@ts-ignore / as any anywhere in the store, providers, API layer, or Route Handlers.store.ts/api.ts.moment and date-fns. Pick date-fns.styled-components (or Emotion) in a Tailwind project.dangerouslySetInnerHTML with API-sourced content that hasn't been sanitized.sessionStorage / localStorage for state that isn't transient client-only UI state. Tokens never go in localStorage — NextAuth handles cookies.proxy.ts (or similarly-named file) at src/ root standing in for middleware.ts.fetch calls in components for backend data. All backend calls go through RTK Query.useState / useReducer first, then useSearchParams, then Redux.createApi() instances. One base api + injectEndpoints per domain.window.location.href = '/auth/login' redirects from inside the base query as the primary 401 handler. Dispatch signOut({ callbackUrl: '/auth/login' }).loading.tsx / error.tsx on authenticated route groups.PrismaClient instances. Use the singleton in src/lib/db.ts.await auth() check. Every handler authenticates first.prisma db push in CI or any non-dev environment. Migrations only.next/core-web-vitals.scaffold-projectpackage.json, tsconfig.json, next.config.ts, tailwind.config.ts, postcss.config.js, components.json, .eslintrc.json (or eslint.config.mjs), .prettierrc, .gitignore, .env.example, README.md. Add docker-compose.yml if Docker was chosen.prisma/schema.prisma with NextAuth tables + sample domain model + (if Credentials) password column. prisma/seed.ts if a test user was requested.src/auth.config.ts (Edge-safe), src/auth.ts (full config with adapter + providers), src/types/next-auth.d.ts.middleware.ts at project root (re-exports / wraps auth).src/app/api/auth/[...nextauth]/route.ts.src/lib/db.ts (Prisma singleton), src/lib/api-auth.ts (requireSession() helper), src/lib/utils.ts, src/lib/zod-utils.ts.src/app/layout.tsx (server, renders Providers), src/app/globals.css, src/app/error.tsx, src/app/not-found.tsx. No src/app/page.tsx that does redirect() — middleware handles /. If a / page is needed (marketing landing), it's 'use client'.src/app/(public)/layout.tsx + auth/login/page.tsx ('use client', calls signIn), src/app/(app)/layout.tsx + _components/AppShell.tsx + loading.tsx + error.tsx, plus (admin)/ if requested.src/redux/store.ts, src/redux/providers.tsx (wraps <SessionProvider> + <Provider>), src/redux/hooks.ts.src/redux/api/api.ts (base, baseUrl: '/api'), src/redux/api/tags.ts.src/components/ui/ with shadcn primitives needed by templates (start with button, input, label, form, dialog, toast).src/components/forms/ composed fields actually used by generated forms.src/components/UnsavedChangesWarning.tsx, src/components/Notifications.tsx.src/config/env.ts (Zod-validated runtime env).tests/unit/ with one reducer test + one schema test + one component test. tests/e2e/ with a Playwright spec that logs in and asserts the dashboard.vitest.config.ts, playwright.config.ts..husky/pre-commit, lint-staged config in package.json..github/workflows/ci.yml with a Postgres service container.pnpm install).prisma generate, prisma migrate dev --name init).pnpm typecheck && pnpm lint && pnpm test && pnpm build — must all pass.add-featureFor feature {{Feature}} in route group {{Group}} (e.g. (app)):
src/app/{{Group}}/{{feature}}/schema.ts — exports {{feature}}Schema (Zod) and inferred {{Feature}}FormValues type.{{Feature}} model to prisma/schema.prisma if it doesn't exist. Run pnpm exec prisma migrate dev --name add-{{feature}}.src/app/api/{{feature}}s/route.ts — GET (list) + POST (create). Both call requireSession(). POST validates the body with {{feature}}Schema.safeParse.src/app/api/{{feature}}s/[id]/route.ts — GET + PATCH + DELETE. All call requireSession(). Ownership check: where: { id, userId: session.user.id }.src/redux/api/{{feature}}sApi.ts with getList, get, create, update, delete endpoints. If the slice doesn't exist, run Mode 3 first.src/app/{{Group}}/{{feature}}s/page.tsx — 'use client'. Uses useGet{{Feature}}sQuery(). Renders {{Feature}}sTable from _components/.src/app/{{Group}}/{{feature}}s/loading.tsx (Suspense fallback).src/app/{{Group}}/{{feature}}s/error.tsx (error boundary).src/app/{{Group}}/{{feature}}s/new/page.tsx — 'use client'. Renders {{Feature}}Form mode="create".src/app/{{Group}}/{{feature}}s/[id]/page.tsx — 'use client'. Renders {{Feature}}Form mode="edit" id={params.id}._components/{{Feature}}sTable.tsx — 'use client', uses useGet{{Feature}}sQuery._components/{{Feature}}Form.tsx — 'use client', uses useForm + zodResolver, wraps <UnsavedChangesWarning>, dispatches create/update mutation on submit.tests/unit/{{feature}}.schema.test.ts (Zod schema valid + invalid). Optional handler test that mocks db and auth.After generating: pnpm typecheck && pnpm test && pnpm build. Apply the Step-5 proof-and-failure protocol — paste the green lines; the same step failing twice = stop and surface the verbatim error.
add-api-sliceFor domain {{domain}} (e.g. customers):
src/redux/api/{{domain}}Api.ts that imports the base api and calls api.injectEndpoints(...). Strongly typed request/response. providesTags on queries, invalidatesTags on mutations.src/redux/api/tags.ts. The tagTypes array on the base api reads from this constant.useGet{{Domain}}sQuery, useCreate{{Domain}}Mutation, etc.).src/app/api/{{domain}}/... — every endpoint needs a handler. See references/templates/route-handler.md.createApi(...). One base api, many injected slices.After generating: pnpm typecheck && pnpm build must pass — Step-5 proof-and-failure protocol applies.
Run this as a mechanical pass over the generated tree — grep, don't recall. The forbidden list is easy to hold at file 1 and forgotten by file 30; do not answer any item from memory of what you intended to write. Start with this grep block — every command must return nothing:
grep -rn "'use server'" src/ # Server Actions
grep -rn "export default async function" src/app --include='page.tsx' # async pages
grep -rn "from '@/lib/db'" src/app --include='*.tsx' # DB access outside route.ts
grep -rn "fetch(" src/app src/components --include='*.tsx' # inline fetch in components
grep -rn "serializableCheck: false\|@ts-ignore\|as any" src/redux src/app/api
grep -rn "moment" src/ package.json # date-fns only
grep -rn "NEXTAUTH_\|getServerSession\|authOptions\|next-auth/next" src/ .env.example # NextAuth v4 leaking in
grep -rn "styled-components\|@emotion" src/ package.json # CSS-in-JS in a Tailwind project
grep -rn "localStorage\|sessionStorage" src/ # any token/session hit is a bug
grep -rn "dangerouslySetInnerHTML" src/ # zero on a fresh scaffold
A hit means fix it and re-run the grep — never rationalize it away. Then the full checklist:
pnpm install succeeds.pnpm exec prisma generate succeeds.pnpm exec prisma migrate dev --name init succeeds (or the user confirmed they ran it manually).pnpm typecheck exits 0.pnpm lint exits 0 (no warnings on a fresh scaffold).pnpm test exits 0.pnpm build succeeds.src/app/** outside route.ts files imports @/lib/db or calls await db.* (server pages don't touch the DB). Grep: grep -rn "from '@/lib/db'" src/app/ --include='*.tsx' returns nothing.route.ts file in src/app/api/** (except [...nextauth]) calls await auth() or await requireSession().'use server' directive anywhere. Grep: grep -rn "'use server'" src/ returns nothing.async function .*Page in any page.tsx. Pages are sync 'use client'.serializableCheck: false, @ts-ignore, as any in src/redux/** or src/app/api/** (covered by the grep block above).createApi( in the codebase (the base api). Grep: grep -rn "createApi(" src/ shows one hit.middleware.ts exists at project root; its config.matcher excludes /api/auth (NextAuth handles its own routes).src/auth.ts and src/auth.config.ts both exist. auth.config.ts has no @/lib/db import (Edge-safe)..env is not committed; .env.example is. AUTH_SECRET is not in .env.example (it's documented as required, with instructions to generate it).If any check fails, fix before reporting.
User: "Scaffold a new Next.js fullstack app with NextAuth and Prisma. Call it acme-portal. Add a dashboard feature too."
Claude:
node --version / pnpm --version. Resolves Next.js, React, NextAuth v5, Prisma, RTK, Tailwind, Zod versions via context7. Quotes them.dashboard feature slice in (app)/dashboard/ with a matching src/app/api/dashboard/route.ts.prisma generate, prisma migrate dev --name init, typecheck, lint, test, build — all must pass.AUTH_SECRET, DATABASE_URL), seeded test credentials (if Credentials provider was chosen), pnpm dev.User: "Add a customers feature end-to-end under the (app) group — list + create + edit."
Claude: Runs Mode 2. If no customersApi exists, runs Mode 3 first. Generates:
prisma/schema.prisma model addition + migration.src/app/api/customers/route.ts (GET list, POST create — both requireSession()-gated, POST validates with customerSchema).src/app/api/customers/[id]/route.ts (GET / PATCH / DELETE — ownership-checked).src/redux/api/customersApi.ts with five endpoints injected.src/app/(app)/customers/page.tsx, new/page.tsx, [id]/page.tsx, loading.tsx, error.tsx — all 'use client'.src/app/(app)/customers/_components/CustomersTable.tsx, CustomerForm.tsx.src/app/(app)/customers/schema.ts (Zod).tests/unit/customer.schema.test.ts.'Customer' + 'Customers' to tags.ts.User: "Add an RTK Query slice for invoices with list, get, create, mark-paid endpoints."
Claude: Runs Mode 3. Creates src/redux/api/invoicesApi.ts injecting four endpoints, adds 'Invoices' + 'Invoice' to tags.ts, generates src/app/api/invoices/route.ts (GET list + POST create), src/app/api/invoices/[id]/route.ts (GET + PATCH), src/app/api/invoices/[id]/mark-paid/route.ts (POST). Adds the Invoice Prisma model and runs prisma migrate dev --name add-invoices. Typechecks.
package.json.src/, except React component files which match the component name in PascalCase (CustomerForm.tsx). Never two paths that differ only by case.session.strategy must be 'jwt' (database sessions don't work with Credentials). The Prisma adapter is still installed because the User table still lives in the DB — just the session is encoded in a JWT cookie. This is by NextAuth design; don't try to switch it to 'database' for Credentials.