Next.js App Router Fullstack Scaffolder (NextAuth + Prisma + RTK Query)
Generate a production-grade Next.js fullstack app where:
- Pages are
'use client'SPA-style. All data goes through RTK Query. Nofetch()in server components. No Server Actions. No asyncpage.tsx. The rootapp/layout.tsxis the only server component that matters — it renders<Providers>. Per-featurepage.tsxfiles are client components. - Backend lives in-app as Next.js Route Handlers under
src/app/api/**/route.ts. Each handler callsawait auth()and queries Postgres via Prisma. - Auth is NextAuth (Auth.js v5), not a custom JWT-cookie scheme.
src/auth.tsis the single source of auth truth.middleware.tsre-exportsauthfor route-level gating. Login UIs callsignIn()fromnext-auth/react; logout callssignOut(). RTK Query reads the session cookie automatically viacredentials: 'include'. - Database is PostgreSQL via Prisma. NextAuth uses the Prisma adapter. The skill confirms the database choice with the user before writing files even though Postgres+Prisma is the only supported option — the connection string and host (local Docker, Neon, Supabase, etc.) need to be locked down up front.
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.
When to use this skill
Trigger on any of:
- "create a new Next.js project" / "scaffold a Next.js app" / "new Next app"
- "Next.js App Router project" / "Next.js + NextAuth" / "Next.js + Prisma"
- "RTK Query Next.js app" / "Redux Toolkit Next.js"
- "shadcn/ui project" / "Tailwind + shadcn + Radix scaffold" in a Next.js context
- "use my Next.js conventions" / "the Next.js patterns I like"
- "add a feature end-to-end" (route + Route Handler + RTK Query + form) in a Next.js context
- "add a new RTK Query API slice for
<domain>" - The user pastes a feature spec for a screen and asks you to wire it through routing + API + state + form
If unsure whether the user wants a brand-new project vs. an addition to an existing one, ask once — don't guess.
Three operating modes
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. |
Workflow
Step 1 — Determine versions (don't hard-code)
Versions are resolved at scaffold time, never hard-pasted into the skill. Before writing package.json:
-
Check the user's environment first: run
node --versionandnpm --version(orpnpm --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/argon2if 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 addmoment)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-staged
-
Quote 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
pnpmis present, otherwise npm. MatchpackageManagerinpackage.json. -
Concrete resolution command when context7 is unavailable:
npm view <pkg> version(e.g.npm view next version;npm view next-auth dist-tags— plainnpm view next-auth versionreturns 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, nolatest/^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:
- NextAuth v4 patterns are POISON here:
authOptionsexport,getServerSession,NEXTAUTH_*env vars, an options object in the[...nextauth]route. v5 isconst { handlers, auth, signIn, signOut } = NextAuth(config)insrc/auth.ts, withAUTH_*env vars. - App Router signatures that changed across majors — e.g. in Next 15
params/searchParamsare Promises in dynamic route segments and Route Handler contexts; check the resolved major before writingparams.id. In'use client'pages read route params withuseParams()fromnext/navigation, not theparamsprop. - RTK Query / Redux Toolkit option names (
fetchBaseQueryoptions,providesTags/invalidatesTagsshapes) and the Prisma client API for the resolved major. - Tailwind majors: v4 dropped the
tailwind.config.ts-first setup for CSS-first config (@import "tailwindcss"+@tailwindcss/postcss); v3 usestailwind.config.ts+postcss.config.js. Emit exactly one style — the one matching the resolved major — never a mix. - Zod majors: v4 moved/renamed APIs (top-level
z.email(), reworked error customization). A v3-only signature against a resolved v4 is a hallucination — check the installed types undernode_modules/zod. - Prisma majors: the
generatorblock shape and default client output location changed across majors. Afterprisma generate, confirm the actual import path from what was generated — don't assume@prisma/clientfrom memory. - The shadcn CLI is
npx shadcn@latest(init/add); the oldshadcn-uipackage 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. - NextAuth v4 patterns are POISON here:
Step 2 — Gather inputs (ask once, in one batch)
For scaffold-project, use AskUserQuestion to collect:
- Project name (kebab-case, used for the directory and
package.jsonname). - Database connection. Postgres + Prisma is the only ORM the skill emits, but the host varies. Offer: local Docker (the skill emits a
docker-compose.ymlwith apostgres:16service), Neon, Supabase, Railway, "I'll paste my ownDATABASE_URL". Confirm before writing.env.exampleandprisma/schema.prisma. - NextAuth providers. Offer: Credentials (email + password, stored in the
Usertable with a bcrypt-hashedpasswordcolumn), GitHub OAuth, Google OAuth, "all of the above". Default: Credentials. The chosen providers determine which.env.examplekeys get emitted (AUTH_GITHUB_ID/AUTH_GITHUB_SECRET, etc.). - Route groups. Default offer:
(public)for auth pages +(app)for authenticated content. Optionally add(admin)for role-gated routes (gated via NextAuth role claim inmiddleware.ts). - First feature/route (optional, e.g.
dashboard) — if provided, runadd-featurefor it after scaffold. - Optional extras: Storybook? i18n? Sentry? Dockerfile? GitHub Actions CI? (Default: skip — only add if asked. CI defaults ON.)
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.
Step 3 — Generate files
Use the templates in references/templates/ as the source of truth. Apply these rules:
- Use
Writefor new files. NeverEditfiles you're creating fresh. - Replace all
{{ProjectName}},{{Feature}},{{Domain}}placeholders consistently.{{project-name}}is kebab-case for files/dirs; PascalCase where namespacing requires. - Create directories before files. On Windows shell use PowerShell
New-Item -ItemType Directory -Force. - Do not run
create-next-appto bootstrap — write files directly from templates so the layout matchesreferences/folder-layout.md. Usenpm/pnpm installafterpackage.jsonis written. - Initialize shadcn/ui by writing
components.jsonfromreferences/templates/components-json.mdandsrc/components/ui/components incrementally as needed (button, input, form, label, dialog, toast, etc.) — don't blanket-install every Radix primitive. - If a generator IS run (
shadcnCLI,prisma init, orcreate-next-appat the user's insistence) and its output differs from this skill's layout: the framework wins on file locations it mandates (middleware.tsplacement,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.
Step 4 — Database setup
After package.json and prisma/schema.prisma are written:
pnpm install(or chosen PM) — installsprismaand@prisma/client.- Confirm with the user that their
DATABASE_URLis reachable before running any DB commands. If they picked local Docker, rundocker compose up -d postgresfirst. 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.- Never run
prisma db pushagainst a production-shaped database. Use migrations. - Never run
prisma migrate resetwithout explicit user confirmation — it drops the DB.
- Never run
- Generate
AUTH_SECRETfor the user:pnpm exec auth secret(Auth.js CLI) oropenssl rand -base64 32. Put it in.env.local(NOT.env.example). - Order is load-bearing:
prisma generateMUST run beforepnpm typecheck/pnpm build— the@prisma/clienttypes 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.
Step 5 — Verify and report
pnpm typecheck(ornpx 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.- A scaffold that hasn't built is not delivered. Paste the actual proof lines in your report (Next's
✓ Compiled successfully, Vitest'sTests 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. - CLI failure protocol (applies to
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. - Reply with a short summary: project path, versions chosen, route groups, env vars to set (especially
AUTH_SECRETandDATABASE_URL), next steps (pnpm dev, log in with the seeded test user if Credentials was chosen).
Project layout (canonical)
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 fromcomponents/,redux/,lib/,config/— never the reverse.app/api/**/route.tsfiles import@/auth,@/lib/db,@/lib/api-auth, and Zod schemas from feature folders — never fromredux/.redux/never imports fromapp/api/**(handlers) orprisma/— RTK Query talks to handlers over HTTP, not through in-process function calls.components/ui/(shadcn primitives) must not import fromapp/,redux/, or feature code.- Feature
_components/and_hooks/are private: only routes inside the same feature folder may import them.
Required code patterns
Full templates are in references/templates/. Rationale for each Keep/Eliminate rule is in references/good-patterns.md and references/anti-patterns.md.
Keep
- Server root layout, client pages.
src/app/layout.tsxis a server component that renders<html><body><Providers>{children}</Providers></body></html>. Every otherpage.tsxis'use client'and uses RTK Query for data. Per-route metadata lives at the layout level (server) — pages can't exportmetadatabecause they're client. Seereferences/templates/root-layout.md. - Route groups for auth boundaries:
(public),(app), optional(admin). Each group has its ownlayout.tsx. The shell (header/sidebar) lives in(app)/layout.tsxrendering an'use client'child from_components/. - NextAuth (Auth.js v5) as the single source of auth truth.
src/auth.tsexports{ handlers, auth, signIn, signOut }.middleware.tsre-exportsauth(or wraps it for role/path logic) with an Edge-safesrc/auth.config.ts. Seereferences/templates/nextauth-config.md. - Route Handlers as the backend. Every
/api/<domain>/route.tscallsawait auth()first; unauthenticated requests 401. Inputs validated with the same Zod schema the form uses. Database access via the Prisma singleton insrc/lib/db.ts. Seereferences/templates/route-handler.md. - One RTK Query base
apiinredux/api/api.tswithbaseUrl: '/api',credentials: 'include', andendpoints: () => ({}). Domain slices callapi.injectEndpoints(...). Tag types are aconstarray. Seereferences/templates/api-base.mdandreferences/templates/api-slice.md. - Auth-aware
baseQuery: on 401, dispatches NextAuthsignOut({ callbackUrl: '/auth/login' }). No hardwindow.location.href = .... The session cookie is sent automatically becausecredentials: 'include'is set and the API is same-origin. - Typed Redux hooks:
useAppDispatch/useAppSelectorfromredux/hooks.ts. Components never use the rawuseDispatch/useSelector. - Strict store config:
serializableCheckleft at the default. Targeted exceptions with comments are fine; blanketfalseis not. No@ts-ignore. - Prisma singleton in
src/lib/db.tsto avoid exhausting connections during dev hot-reload. Seereferences/templates/db-client.md. - Prisma schema covers NextAuth + domain. NextAuth tables (
User,Account,Session,VerificationToken) plus apasswordcolumn onUserfor Credentials. Domain models live in the same file. Seereferences/templates/prisma-schema.md. - shadcn/ui + Tailwind + Radix: shadcn primitives in
components/ui/(owned by the project, not a node_modules dependency). Composed form fields incomponents/forms/.cn()util inlib/utils.tsis the single source of class merging. - React Hook Form + Zod: every form uses
useForm({ resolver: zodResolver(schema), defaultValues: getDefaultValuesFromSchema(unwrapZodEffects(schema)) }). The same Zod schema validates request bodies in the matching Route Handler. Seereferences/templates/form-with-zod.md. UnsavedChangesWarningwired to React Hook Form'sformState.isDirty.- Per-feature
_components/and_hooks/private to that route. error.tsxandloading.tsxat every meaningful route segment.- Runtime env validation in
src/config/env.tsusing Zod.AUTH_SECRET,DATABASE_URL,AUTH_URL(production), and any OAuth provider keys are required. - Single date library:
date-fns. Nomoment. - Minimal comments in generated code. Default to no comments. Only add one when the why is non-obvious — a workaround for a specific upstream issue (with a link), a subtle invariant the code depends on, a domain rule that isn't visible from the names. Never write block headers, never restate what the next line does, never leave
// TODOwithout 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.jsonstrict +@/*path alias — seereferences/templates/tsconfig.md.- Vitest + RTL for unit tests, Playwright for E2E. At least: one reducer test, one schema test, one component test, one E2E auth-then-dashboard flow.
- Husky + lint-staged pre-commit:
prettier --write+eslint --fixon staged files. - GitHub Actions CI running install → lint → typecheck → test → build on every PR. Tests use a throwaway Postgres service container.
Eliminate (anti-patterns)
Every one of these is forbidden in generated code. Rationale in references/anti-patterns.md.
- ❌
fetch()orawait db.*inside a server component (serverpage.tsx,layout.tsx, or any non-'use client'file underapp/that isn'troute.ts). All data goes through RTK Query → Route Handlers. The app is "API-driven, not SSR" by deliberate choice. - ❌ Server Actions (
'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 serverpage.tsxas the auth-gate fallback. The auth gate ismiddleware.ts. The root/redirect is also middleware's job (e.g. send signed-in users to/app/dashboard, signed-out to/auth/login). - ❌ Custom JWT-in-
httpOnly-cookie schemes alongside NextAuth. Pick one. The default is NextAuth — don't generate a parallelsetCookie('session', ...)flow in a Route Handler. - ❌ Decoding the NextAuth JWT manually on the client to drive auth decisions. Use
useSession()fromnext-auth/react(or callawait auth()server-side in Route Handlers). - ❌
serializableCheck: falseon the store config without targetedignoredPaths/ignoredActionsand a comment explaining the exception. - ❌
@ts-ignore/as anyanywhere in the store, providers, API layer, or Route Handlers. - ❌ Commented-out reducers, endpoints, or imports left in
store.ts/api.ts. - ❌ Mixing
momentanddate-fns. Pickdate-fns. - ❌
styled-components(or Emotion) in a Tailwind project. - ❌ Two folders or files that differ only by case (breaks on Linux CI).
- ❌
dangerouslySetInnerHTMLwith API-sourced content that hasn't been sanitized. - ❌
sessionStorage/localStoragefor state that isn't transient client-only UI state. Tokens never go inlocalStorage— NextAuth handles cookies. - ❌ A
proxy.ts(or similarly-named file) atsrc/root standing in formiddleware.ts. - ❌ Inline
fetchcalls in components for backend data. All backend calls go through RTK Query. - ❌ Redux slices for state that should be local component state, URL state, or React Hook Form state. Reach for
useState/useReducerfirst, thenuseSearchParams, then Redux. - ❌ Many independent
createApi()instances. One baseapi+injectEndpointsper domain. - ❌ Hard
window.location.href = '/auth/login'redirects from inside the base query as the primary 401 handler. DispatchsignOut({ callbackUrl: '/auth/login' }). - ❌ Skipping
loading.tsx/error.tsxon authenticated route groups. - ❌ Multiple
PrismaClientinstances. Use the singleton insrc/lib/db.ts. - ❌ Route Handlers that skip the
await auth()check. Every handler authenticates first. - ❌ Route Handlers that trust client-sent user IDs for ownership. Read the user ID from the session, not the request body.
- ❌
prisma db pushin CI or any non-dev environment. Migrations only. - ❌ No tests at all.
- ❌ ESLint extending only
next/core-web-vitals.
Operating-mode playbooks
Mode 1 — scaffold-project
- Resolve versions per Step 1. Quote them.
- Ask the inputs per Step 2 (database host, NextAuth providers, route groups, optional extras). Wait for answers.
- Generate, in this order:
package.json,tsconfig.json,next.config.ts,tailwind.config.ts,postcss.config.js,components.json,.eslintrc.json(oreslint.config.mjs),.prettierrc,.gitignore,.env.example,README.md. Adddocker-compose.ymlif Docker was chosen.prisma/schema.prismawith NextAuth tables + sample domain model + (if Credentials)passwordcolumn.prisma/seed.tsif 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.tsat project root (re-exports / wrapsauth).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. Nosrc/app/page.tsxthat doesredirect()— middleware handles/. If a/page is needed (marketing landing), it's'use client'.- Route groups:
src/app/(public)/layout.tsx+auth/login/page.tsx('use client', callssignIn),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-stagedconfig inpackage.json..github/workflows/ci.ymlwith a Postgres service container.
- Install dependencies (
pnpm install). - Database setup per Step 4 (
prisma generate,prisma migrate dev --name init). pnpm typecheck && pnpm lint && pnpm test && pnpm build— must all pass.- If a first feature was requested, immediately run Mode 2 for that feature.
- Report.
Mode 2 — add-feature
For feature {{Feature}} in route group {{Group}} (e.g. (app)):
- Schema (shared between form and Route Handler):
src/app/{{Group}}/{{feature}}/schema.ts— exports{{feature}}Schema(Zod) and inferred{{Feature}}FormValuestype. - Prisma model: add a
{{Feature}}model toprisma/schema.prismaif it doesn't exist. Runpnpm exec prisma migrate dev --name add-{{feature}}. - Route Handlers:
src/app/api/{{feature}}s/route.ts—GET(list) +POST(create). Both callrequireSession().POSTvalidates the body with{{feature}}Schema.safeParse.src/app/api/{{feature}}s/[id]/route.ts—GET+PATCH+DELETE. All callrequireSession(). Ownership check:where: { id, userId: session.user.id }.
- RTK Query slice: extend
src/redux/api/{{feature}}sApi.tswithgetList,get,create,update,deleteendpoints. If the slice doesn't exist, run Mode 3 first. - Routing:
src/app/{{Group}}/{{feature}}s/page.tsx—'use client'. UsesuseGet{{Feature}}sQuery(). Renders{{Feature}}sTablefrom_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}.
- Private components:
_components/{{Feature}}sTable.tsx—'use client', usesuseGet{{Feature}}sQuery._components/{{Feature}}Form.tsx—'use client', usesuseForm+zodResolver, wraps<UnsavedChangesWarning>, dispatches create/update mutation on submit.
- Tests:
tests/unit/{{feature}}.schema.test.ts(Zod schema valid + invalid). Optional handler test that mocksdbandauth.
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.
Mode 3 — add-api-slice
For domain {{domain}} (e.g. customers):
- Create
src/redux/api/{{domain}}Api.tsthat imports the baseapiand callsapi.injectEndpoints(...). Strongly typed request/response.providesTagson queries,invalidatesTagson mutations. - Add new tag types to
src/redux/api/tags.ts. ThetagTypesarray on the baseapireads from this constant. - Export generated hooks (
useGet{{Domain}}sQuery,useCreate{{Domain}}Mutation, etc.). - Create the matching Route Handlers under
src/app/api/{{domain}}/...— every endpoint needs a handler. Seereferences/templates/route-handler.md. - Add the Prisma model(s) and migrate if needed.
- Do not create a new
createApi(...). One baseapi, many injected slices.
After generating: pnpm typecheck && pnpm build must pass — Step-5 proof-and-failure protocol applies.
Verification checklist before reporting "done"
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 installsucceeds. -
pnpm exec prisma generatesucceeds. -
pnpm exec prisma migrate dev --name initsucceeds (or the user confirmed they ran it manually). -
pnpm typecheckexits 0. -
pnpm lintexits 0 (no warnings on a fresh scaffold). -
pnpm testexits 0. -
pnpm buildsucceeds. - No file under
src/app/**outsideroute.tsfiles imports@/lib/dbor callsawait db.*(server pages don't touch the DB). Grep:grep -rn "from '@/lib/db'" src/app/ --include='*.tsx'returns nothing. - Every
route.tsfile insrc/app/api/**(except[...nextauth]) callsawait auth()orawait requireSession(). - No
'use server'directive anywhere. Grep:grep -rn "'use server'" src/returns nothing. - No
async function .*Pagein anypage.tsx. Pages are sync'use client'. - No
serializableCheck: false,@ts-ignore,as anyinsrc/redux/**orsrc/app/api/**(covered by the grep block above). - Exactly one
createApi(in the codebase (the baseapi). Grep:grep -rn "createApi(" src/shows one hit. -
middleware.tsexists at project root; itsconfig.matcherexcludes/api/auth(NextAuth handles its own routes). -
src/auth.tsandsrc/auth.config.tsboth exist.auth.config.tshas no@/lib/dbimport (Edge-safe). -
.envis not committed;.env.exampleis.AUTH_SECRETis not in.env.example(it's documented as required, with instructions to generate it). - No duplicate-by-case folders.
If any check fails, fix before reporting.
Examples
Example 1: Fresh fullstack project
User: "Scaffold a new Next.js fullstack app with NextAuth and Prisma. Call it acme-portal. Add a dashboard feature too."
Claude:
- Runs
node --version/pnpm --version. Resolves Next.js, React, NextAuth v5, Prisma, RTK, Tailwind, Zod versions via context7. Quotes them. - Asks Step-2 questions: DB host (local Docker / Neon / Supabase / own), NextAuth providers (Credentials / GitHub / Google / all), route groups, extras.
- Generates the full project + the
dashboardfeature slice in(app)/dashboard/with a matchingsrc/app/api/dashboard/route.ts. - Runs install,
prisma generate,prisma migrate dev --name init, typecheck, lint, test, build — all must pass. - Reports: project path, env vars to set (
AUTH_SECRET,DATABASE_URL), seeded test credentials (if Credentials provider was chosen),pnpm dev.
Example 2: Add a feature
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.prismamodel addition + migration.src/app/api/customers/route.ts(GET list, POST create — bothrequireSession()-gated, POST validates withcustomerSchema).src/app/api/customers/[id]/route.ts(GET / PATCH / DELETE — ownership-checked).src/redux/api/customersApi.tswith 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.- Adds
'Customer'+'Customers'totags.ts.
Example 3: Add an API slice + handlers
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.
Notes
- Don't over-engineer. No Storybook, i18n, Sentry, MSW, tRPC, or Server Actions wrappers unless the user asks. The default scaffold is intentionally lean.
- Don't rewrite the user's existing project. This skill is for new scaffolds (and additive feature/slice modes), not migrations.
- Versions: always quote the resolved package versions before writing
package.json. - Naming: kebab-case for files and folders inside
src/, except React component files which match the component name in PascalCase (CustomerForm.tsx). Never two paths that differ only by case. - NextAuth Credentials caveat: when Credentials is the only/primary provider,
session.strategymust 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. - API-driven means API-driven. If the user asks for SSR data fetching or Server Actions mid-scaffold, push back with the rationale (consistency, single mental model, easier mock/integration testing) before complying. Don't silently switch modes.
