agentleFS
dennisrongodennisrongo/claude-skills/skills/nextjs-app-router/SKILL.md

nextjs-app-router

Skill1 starsChanged 3 months agoMITView on GitHub

Scaffold 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.

  • Reads credentials

13 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use skill-creator.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • SentrySkill

    skill-creator

    Create new agent skills following the Agent Skills specification. Use when asked to "create a skill", "add a new skill", "write a skill", "make a skill", "build a skill", or scaffold a new skill with SKILL.md. Guides through requirements, writing, registration, and verification.

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

28 rules. 0 are common, 7 are distinctive, and 21 are very specific to this project.

  • 0

    Common

    Most of the 11 edited versions we read keep these.

  • 7

    Distinctive

    This author's own choices. Worth a look.

  • 21

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Pages are 'use client' SPA-style: All data goes through RTK Query.

    line 19
  2. Distinctive:

    If unsure whether the user wants a brand-new project vs.

    line 39
  3. Very specific:

    Check the user's environment first: run node --version and npm --version (or pnpm --version / yarn --version).

    line 57
  4. Very specific:

    Prefer the latest stable Next.js (App Router GA from 13.4.

    line 70
  5. Very specific:

    Default to pnpm if pnpm is present, otherwise npm.

    line 71
  6. Very specific:

    If you are not certain a symbol exists in the resolved version, verify it (read the installed types under node_modules/<pkg>/, or check …

    line 82
  7. Very specific:

    Route groups: Default offer: (public) for auth pages + (app) for authenticated content.

    line 91
  8. Very specific:

    Use the templates in references/templates/ as the source of truth.

    line 101
  9. Very specific:

    Use Write for new files. Never Edit files you're creating fresh.

    line 103
  10. Very specific:

    Create directories before files.

    line 105
  11. Very specific:

    Do not run create-next-app to bootstrap — write files directly from templates so the layout matches references/folder-layout.md.

    line 106
  12. Very specific:

    After package.json and prisma/schema.prisma are written:

    line 112
  13. Very specific:

    Route Handlers as the backend: Every /api/<domain>/route.ts calls await auth() first.

    line 226
  14. Very specific:

    React Hook Form + Zod: every form uses useForm({ resolver: zodResolver(schema), defaultValues: …

    line 234
  15. Very specific:

    Every one of these is forbidden in generated code.

    line 248
  16. Distinctive:

    Ask the inputs per Step 2 (database host, NextAuth providers, route groups, optional extras).

    line 282
  17. Distinctive:

    If a first feature was requested, immediately run Mode 2 for that feature.

    line 305
  18. Very specific:

    Prisma model: add a {{Feature}} model to prisma/schema.prisma if it doesn't exist.

    line 313
  19. Very specific:

    After generating: pnpm typecheck && pnpm test && pnpm build.

    line 329
  20. Very specific:

    Create src/redux/api/{{domain}}Api.ts that imports the base api and calls api.injectEndpoints(...).

    line 335
  21. Very specific:

    Add new tag types to src/redux/api/tags.ts.

    line 336
  22. Distinctive:

    Add the Prisma model(s) and migrate if needed.

    line 339
  23. Very specific:

    Do not: create a new createApi(...).

    line 340
  24. Very specific:

    After generating: pnpm typecheck && pnpm build must pass — Step-5 proof-and-failure protocol applies.

    line 342
  25. Distinctive:

    If any check fails, fix before reporting.

    line 381
  26. Distinctive:

    Don't over-engineer: No Storybook, i18n, Sentry, MSW, tRPC, or Server Actions wrappers unless the user asks.

    line 419
  27. Very specific:

    Versions: always quote the resolved package versions before writing package.json.

    line 421
  28. Distinctive:

    API-driven means API-driven: If the user asks for SSR data fetching or Server Actions mid-scaffold, push back with the rationale …

    line 424

“Common” is measured: kept by at least 6 in 10 of the 11 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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. 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.
  • Backend lives in-app as Next.js Route Handlers under src/app/api/**/route.ts. Each handler calls await auth() and queries Postgres via Prisma.
  • Auth is NextAuth (Auth.js v5), not a custom JWT-cookie scheme. 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'.
  • 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.

ModeTriggerOutput
scaffold-project"new project", "scaffold app", empty directoryFull 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:

  1. Check the user's environment first: run node --version and npm --version (or pnpm --version / yarn --version).
  2. 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/node
    • next-auth (Auth.js v5 — current beta as of late 2025; verify via context7), @auth/prisma-adapter
    • prisma, @prisma/client, bcryptjs (and @types/bcryptjs) — or @node-rs/argon2 if user prefers Argon2
    • @reduxjs/toolkit, react-redux
    • tailwindcss, postcss, autoprefixer
    • @radix-ui/* (only the primitives the templates need), class-variance-authority, clsx, tailwind-merge, tailwindcss-animate, lucide-react
    • react-hook-form, @hookform/resolvers, zod
    • date-fns (do not also add moment)
    • vitest, @testing-library/react, @testing-library/jest-dom, jsdom, @playwright/test
    • eslint, eslint-config-next, @typescript-eslint/*, eslint-plugin-react-hooks, eslint-plugin-jsx-a11y, prettier, husky, lint-staged
  3. Quote the resolved versions back to the user before generating, so they can object.
  4. Prefer the latest stable Next.js (App Router GA from 13.4; resolve current).
  5. Default to pnpm if pnpm is present, otherwise npm. Match packageManager in package.json.
  6. 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.
  7. 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: 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.
    • App Router signatures that changed across majors — e.g. in Next 15 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.
    • RTK Query / Redux Toolkit option names (fetchBaseQuery options, providesTags/invalidatesTags shapes) 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 uses tailwind.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 under node_modules/zod.
    • Prisma majors: the 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.
    • The shadcn CLI is 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.

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.json name).
  • Database connection. Postgres + Prisma is the only ORM the skill emits, but the host varies. Offer: local Docker (the skill emits a 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.
  • NextAuth providers. Offer: Credentials (email + password, stored in the 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.).
  • Route groups. Default offer: (public) for auth pages + (app) for authenticated content. Optionally add (admin) for role-gated routes (gated via NextAuth role claim in middleware.ts).
  • First feature/route (optional, e.g. dashboard) — if provided, run add-feature for 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 Write for new files. Never Edit files 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-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.
  • Initialize shadcn/ui by writing 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.
  • If a generator IS run (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.

Step 4 — Database setup

After package.json and prisma/schema.prisma are written:

  1. pnpm install (or chosen PM) — installs prisma and @prisma/client.
  2. Confirm with the user that their DATABASE_URL is reachable before running any DB commands. If they picked local Docker, run docker compose up -d postgres first.
  3. pnpm exec prisma generate — generates the typed client.
  4. pnpm exec prisma migrate dev --name init — creates the initial migration covering NextAuth tables + sample domain models.
    • Never run prisma db push against a production-shaped database. Use migrations.
    • Never run prisma migrate reset without explicit user confirmation — it drops the DB.
  5. Generate 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).
  6. Order is load-bearing: 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.

Step 5 — Verify and report

  • 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.
  • A scaffold that hasn't built is not delivered. Paste the actual proof lines in your report (Next's ✓ 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.
  • 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_SECRET and DATABASE_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 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.
  • 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.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.
  • Route groups for auth boundaries: (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/.
  • NextAuth (Auth.js v5) as the single source of auth truth. 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.
  • Route Handlers as the backend. Every /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.
  • One RTK Query base 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.
  • Auth-aware 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.
  • Typed Redux hooks: useAppDispatch/useAppSelector from redux/hooks.ts. Components never use the raw useDispatch/useSelector.
  • Strict store config: serializableCheck left at the default. Targeted exceptions with comments are fine; blanket false is not. No @ts-ignore.
  • Prisma singleton in src/lib/db.ts to avoid exhausting connections during dev hot-reload. See references/templates/db-client.md.
  • Prisma schema covers NextAuth + domain. NextAuth tables (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.
  • shadcn/ui + Tailwind + Radix: shadcn primitives in 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.
  • 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. See references/templates/form-with-zod.md.
  • UnsavedChangesWarning wired to React Hook Form's formState.isDirty.
  • Per-feature _components/ and _hooks/ private to that route.
  • error.tsx and loading.tsx at every meaningful route segment.
  • Runtime env validation in src/config/env.ts using Zod. AUTH_SECRET, DATABASE_URL, AUTH_URL (production), and any OAuth provider keys are required.
  • Single date library: date-fns. No moment.
  • 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 // 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.
  • 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 --fix on 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() 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.
  • ❌ 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 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).
  • ❌ Custom JWT-in-httpOnly-cookie schemes alongside NextAuth. Pick one. The default is NextAuth — don't generate a parallel setCookie('session', ...) flow in a Route Handler.
  • ❌ Decoding the NextAuth JWT manually on the client to drive auth decisions. Use 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.
  • ❌ Commented-out reducers, endpoints, or imports left in store.ts/api.ts.
  • ❌ Mixing moment and date-fns. Pick date-fns.
  • ❌ styled-components (or Emotion) in a Tailwind project.
  • ❌ Two folders or files that differ only by case (breaks on Linux CI).
  • ❌ 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.
  • ❌ A proxy.ts (or similarly-named file) at src/ root standing in for middleware.ts.
  • ❌ Inline fetch calls 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 / useReducer first, then useSearchParams, then Redux.
  • ❌ Many independent createApi() instances. One base api + injectEndpoints per domain.
  • ❌ Hard window.location.href = '/auth/login' redirects from inside the base query as the primary 401 handler. Dispatch signOut({ callbackUrl: '/auth/login' }).
  • ❌ Skipping loading.tsx / error.tsx on authenticated route groups.
  • ❌ Multiple PrismaClient instances. Use the singleton in src/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 push in 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

  1. Resolve versions per Step 1. Quote them.
  2. Ask the inputs per Step 2 (database host, NextAuth providers, route groups, optional extras). Wait for answers.
  3. Generate, in this order:
    1. package.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.
    2. prisma/schema.prisma with NextAuth tables + sample domain model + (if Credentials) password column. prisma/seed.ts if a test user was requested.
    3. src/auth.config.ts (Edge-safe), src/auth.ts (full config with adapter + providers), src/types/next-auth.d.ts.
    4. middleware.ts at project root (re-exports / wraps auth).
    5. src/app/api/auth/[...nextauth]/route.ts.
    6. src/lib/db.ts (Prisma singleton), src/lib/api-auth.ts (requireSession() helper), src/lib/utils.ts, src/lib/zod-utils.ts.
    7. 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'.
    8. Route groups: 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.
    9. src/redux/store.ts, src/redux/providers.tsx (wraps <SessionProvider> + <Provider>), src/redux/hooks.ts.
    10. src/redux/api/api.ts (base, baseUrl: '/api'), src/redux/api/tags.ts.
    11. src/components/ui/ with shadcn primitives needed by templates (start with button, input, label, form, dialog, toast).
    12. src/components/forms/ composed fields actually used by generated forms.
    13. src/components/UnsavedChangesWarning.tsx, src/components/Notifications.tsx.
    14. src/config/env.ts (Zod-validated runtime env).
    15. 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.
    16. vitest.config.ts, playwright.config.ts.
    17. .husky/pre-commit, lint-staged config in package.json.
    18. .github/workflows/ci.yml with a Postgres service container.
  4. Install dependencies (pnpm install).
  5. Database setup per Step 4 (prisma generate, prisma migrate dev --name init).
  6. pnpm typecheck && pnpm lint && pnpm test && pnpm build — must all pass.
  7. If a first feature was requested, immediately run Mode 2 for that feature.
  8. Report.

Mode 2 — add-feature

For feature {{Feature}} in route group {{Group}} (e.g. (app)):

  1. Schema (shared between form and Route Handler): src/app/{{Group}}/{{feature}}/schema.ts — exports {{feature}}Schema (Zod) and inferred {{Feature}}FormValues type.
  2. Prisma model: add a {{Feature}} model to prisma/schema.prisma if it doesn't exist. Run pnpm exec prisma migrate dev --name add-{{feature}}.
  3. Route Handlers:
    • 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 }.
  4. RTK Query slice: extend src/redux/api/{{feature}}sApi.ts with getList, get, create, update, delete endpoints. If the slice doesn't exist, run Mode 3 first.
  5. Routing:
    • 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}.
  6. Private components:
    • _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.
  7. Tests: 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.

Mode 3 — add-api-slice

For domain {{domain}} (e.g. customers):

  1. Create 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.
  2. Add new tag types to src/redux/api/tags.ts. The tagTypes array on the base api reads from this constant.
  3. Export generated hooks (useGet{{Domain}}sQuery, useCreate{{Domain}}Mutation, etc.).
  4. Create the matching Route Handlers under src/app/api/{{domain}}/... — every endpoint needs a handler. See references/templates/route-handler.md.
  5. Add the Prisma model(s) and migrate if needed.
  6. Do not create a new createApi(...). One base api, 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 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.
  • No file under 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.
  • Every route.ts file in src/app/api/** (except [...nextauth]) calls await auth() or await requireSession().
  • No 'use server' directive anywhere. Grep: grep -rn "'use server'" src/ returns nothing.
  • No async function .*Page in any page.tsx. Pages are sync 'use client'.
  • No serializableCheck: false, @ts-ignore, as any in src/redux/** or src/app/api/** (covered by the grep block above).
  • Exactly one 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).
  • 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:

  1. Runs node --version / pnpm --version. Resolves Next.js, React, NextAuth v5, Prisma, RTK, Tailwind, Zod versions via context7. Quotes them.
  2. Asks Step-2 questions: DB host (local Docker / Neon / Supabase / own), NextAuth providers (Credentials / GitHub / Google / all), route groups, extras.
  3. Generates the full project + the dashboard feature slice in (app)/dashboard/ with a matching src/app/api/dashboard/route.ts.
  4. Runs install, prisma generate, prisma migrate dev --name init, typecheck, lint, test, build — all must pass.
  5. 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.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.
  • Adds 'Customer' + 'Customers' to tags.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.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.
  • 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.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.