Skip to content

To get correct Next.js 16 code from an AI agent, make three moves: point the agent at the docs bundled in your installed next package so it stops writing Next.js 14 and 15 code, connect it to the running dev server through next-devtools-mcp, and let next build, tsc and Playwright tests prove each change. The recipes target Next.js 16 and the App Router.

This page is for developers who let Claude Code, Codex or Cursor write a Next.js App Router application, and for the tech lead who has to trust the result. The agent’s dashboard renders, but it creates middleware.ts with export const runtime = 'edge', calls revalidateTag('posts') with one argument, and puts 'use client' at the top of a page. Everything compiles, the build warns, and nobody reads the warnings.

  • A context setup that makes the agent read version-matched Next.js docs instead of its training data
  • The Next.js DevTools MCP server and the next-dev-loop skill wired into all three tools, so the agent sees runtime errors itself
  • Eight prompts for the App Router work agents most often get wrong: streaming, Server Actions, auth, modals, caching, Proxy, Core Web Vitals, and tenant isolation
  • A gate table that signs a change off without anyone reading every line, and a failure-mode table with recovery steps

Give the agent version-matched Next.js docs first

Section titled “Give the agent version-matched Next.js docs first”

Agents learned Next.js from years of Pages Router, Next.js 13 to 15, and Vercel blog posts. Since Next.js 16.2, the next package ships its own docs in node_modules/next/dist/docs/, matched to the version you installed. Your job is to make the agent read them before it writes code.

  1. Check which case applies. On Next.js 16.3 or later, next dev detects a coding agent and writes a managed block into AGENTS.md, plus a CLAUDE.md that contains @AGENTS.md. Commit both files. On 16.2, write the AGENTS.md instruction yourself. On 16.1 or earlier, run npx @next/codemod@canary agents-md in the project root to download version-matched docs into .next-docs/.

  2. Add your project rules to AGENTS.md outside the <!-- BEGIN:nextjs-agent-rules --> markers. next dev rewrites everything inside them.

    ## Next.js rules for this repo (Next.js 16.3, App Router only)
    - Read node_modules/next/dist/docs/ before using any Next.js API. Heed deprecation notices.
    - Request interception lives in proxy.ts (export `proxy`). Never create middleware.ts.
    - No `export const runtime = 'edge'`. The Edge Runtime is deprecated.
    - cacheComponents is on: use 'use cache' + cacheLife + cacheTag, never `revalidate`,
    `dynamic` or `dynamicParams` segment exports.
    - revalidateTag(tag, 'max') in Route Handlers; updateTag(tag) in Server Actions.
    - 'use client' only on leaf interactive components, never on page.tsx or layout.tsx.
    - Every Server Action checks the session and the user's permission itself.
    - Done means: next typegen && tsc --noEmit, eslint, next build and Playwright all pass.
  3. Put the stale patterns in front of the reviewer too. These are the APIs the agent reaches for first, checked against the bundled docs of next@16.3.6:

    Agent writesStatus in Next.js 16Write instead
    middleware.ts with export function middlewareDeprecated, renamed in 16.0.0proxy.ts with export function proxy (codemod: npx @next/codemod@canary middleware-to-proxy .)
    export const runtime = 'edge'Edge Runtime deprecated; not supported in proxy.ts or with Cache ComponentsRemove the export; Node.js is the default
    revalidateTag('posts')One-argument form deprecated, TypeScript errorrevalidateTag('posts', 'max'), or updateTag('posts') inside a Server Action
    experimental.ppr or experimental_pprRemoved in 16cacheComponents: true in next.config.ts
    export const revalidate = 3600 on a pageRemoved when Cache Components is on'use cache' with cacheLife('hours')
    request.geo, request.ipRemoved in 15Your host’s headers, or geolocation() from @vercel/functions on Vercel
    dynamic(() => import(...), { ssr: false }) in a Server ComponentBuild errorMove the call into a Client Component
    next lintRemoved in 16eslint directly (codemod: next-lint-to-eslint-cli)
    A parallel route slot without default.tsxBuild fails in 16A default.tsx that returns null or calls notFound()
    <Image priority>Deprecated in 16.0.0fetchPriority="high" or loading="eager" (or preload) on the one LCP image

Connect the agent to the running Next.js dev server

Section titled “Connect the agent to the running Next.js dev server”

A type check proves the code compiles. It does not show the hydration error in the browser console or the route that blocks prerendering. Next.js 16 dev servers expose an MCP endpoint at /_next/mcp, and the next-devtools-mcp package (0.4.0) connects agents to it. Its tools include get_errors, get_routes and get_logs, plus two compile tools that work without a full next build: compile_route, which compiles one route on demand, and get_compilation_issues, which returns the project’s current compile errors. Both compile tools are Turbopack only. See dev-server MCP servers for Next.js and other frameworks for the details.

Terminal window
# Terminal, in the project root. -s project writes .mcp.json so the team shares it.
claude mcp add -s project next-devtools -- npx -y next-devtools-mcp@latest
npx skills add vercel/next.js --skill next-dev-loop

Start npm run dev in a second terminal, then run /mcp in the session to confirm next-devtools is connected.

next-dev-loop needs Next.js 16.3 or later on Turbopack and the agent-browser CLI 0.31.1 or later: npm install -g agent-browser && agent-browser install. When either is missing, the skill tells you how to upgrade and stops instead of running.

The next-dev-loop skill turns that connection into an inspect, edit and verify cycle, using the MCP server and the agent-browser CLI for the browser’s view. For page-level browser checks beyond that, add Playwright MCP or the Playwright CLI. The other Next.js skills in vercel/next.js (next-cache-components-adoption, next-cache-components-optimizer, next-partial-prefetching-adoption) are listed with the rest of the frontend skills.

Stream a dashboard with independent Suspense boundaries

Section titled “Stream a dashboard with independent Suspense boundaries”

Your dashboard makes four independent data calls, and the page waits for the slowest one. The fix is one <Suspense> boundary per section. The failure you are preventing is the agent wrapping everything in Promise.all or moving the page to the client.

The instant() helper from @next/playwright scopes assertions to the UI available the moment the user navigates. The test fails if a later change makes the route block again, which is the regression a code review almost never catches.

Build forms with Server Actions that check authorization

Section titled “Build forms with Server Actions that check authorization”

Server Actions look like function calls, but the Next.js docs are explicit that they are “reachable via direct POST requests”. An agent that checks the session in proxy.ts and trusts it inside the action leaves the endpoint open to anyone who replays the request.

For an action that changes the signed-in user’s own data, add one line to the prompt: “call updateTag for the tag the page reads, so the user sees the change on the next render”. updateTag works only inside Server Actions; a webhook Route Handler uses revalidateTag(tag, 'max').

Protect routes with Proxy and a data access layer

Section titled “Protect routes with Proxy and a data access layer”

Auth is where agents blur the server and client boundary most. In Next.js 16, proxy.ts runs on the Node.js runtime and the docs recommend it only for optimistic checks, reading the session cookie without a database call. The real check belongs next to the data. Pick one library before you prompt: Auth.js v5 is still on the beta tag, and Better Auth (better-auth 1.7.6) is the other common choice for App Router projects.

Start in plan mode (/plan) so you review the file list before any code exists. After the change, run /code-review and ask it to list every Server Action and Route Handler that does not call verifySession(). For CI, the same audit runs headless with claude -p "…" --tools "Read,Grep,Glob" so the reviewer cannot edit what it audits.

Open product details in a modal with intercepting routes

Section titled “Open product details in a modal with intercepting routes”

Product details should open in a modal from the grid and as a full page on a direct visit or refresh. Agents usually get the folder names right and miss two things: the default.tsx that Next.js 16 now requires for every parallel slot, and the behaviour on refresh.

Cache a blog with Cache Components and on-demand revalidation

Section titled “Cache a blog with Cache Components and on-demand revalidation”

The old recipe for a 500-post blog was Incremental Static Regeneration with export const revalidate. With cacheComponents: true, those segment exports are gone. Caching is declared on the data function with 'use cache', and a CMS webhook expires the right tag.

Run A/B tests and feature flags in proxy.ts

Section titled “Run A/B tests and feature flags in proxy.ts”

Proxy is the right place for a cookie-based variant split and for passing flags to Server Components as request headers. It is the wrong place for anything slow, because it runs on every matched request, prefetches included.

Fix Core Web Vitals with a budget CI enforces

Section titled “Fix Core Web Vitals with a budget CI enforces”

Lighthouse reports a performance score of 65, LCP of 4.2 s and CLS of 0.25. The edits are routine: next/image with fetchPriority="high" on the one LCP image, next/font, skeletons with fixed dimensions, and useTransition on the filter. The part that lasts is the budget in CI, so the next agent-written change cannot undo them.

Add the Chrome DevTools MCP server (claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest, version 1.10.1) so the agent records a performance trace against http://localhost:3000 and reads the LCP element itself, instead of guessing which image it is.

Lighthouse is a lab test with no user input, so it cannot measure Interaction to Next Paint. Total blocking time is the lab proxy; field INP comes from useReportWebVitals in real sessions. Google’s web.dev Core Web Vitals thresholds (checked 2026-09-26) put “good” at LCP up to 2.5 s, CLS up to 0.1 and INP up to 200 ms. For a load-testing setup beyond the page, see performance testing with agents.

A SaaS that routes tenants by subdomain fails in one way that matters: one tenant sees another tenant’s rows. A tenant-scoped query wrapper plus a test that tries to cross the boundary is the only proof that holds after the agent’s next refactor.

For end-to-end type safety between server and client, tRPC 11 (@trpc/server 11.19.0) remains an option. Server Actions and Route Handlers with PageProps types cover most App Router apps without it. Whichever you choose, the proof is the same: change a server return type and confirm tsc --noEmit fails in the consuming component.

How to verify agent-written Next.js code without reading every line

Section titled “How to verify agent-written Next.js code without reading every line”

Read the diffs of proxy.ts, lib/dal.ts, next.config.ts and any file that exports a Server Action. Let the gates prove the rest.

GateCommand or toolWhat goes red
Typesnpx next typegen && npx tsc --noEmitSync params, wrong PageProps, a one-argument revalidateTag, a server type change the client did not follow
Lintnpx eslint . plus no-restricted-imports for server-only modules and the raw database clientServer code imported into a Client Component; tenant queries that skip the scoped client
Buildnpx next build (add --debug-prerender when the build error is not enough)Routes that block prerendering, ssr: false in a Server Component, a slot without default.tsx
Runtimeget_errors and compile_route through next-devtools-mcpHydration errors and runtime errors that type checks miss
BehaviourPlaywright, with instant() from @next/playwright for navigationsAuth bypass, modal navigation, streaming regressions, tenant crossing
Performancenpx @lhci/cli autorun with the budgets aboveLCP and CLS regressions, heavy JavaScript

Make the agent run the same gates before it says it is done. In Claude Code, add a Stop hook to .claude/settings.json that runs npx tsc --noEmit && npx next build and exits with code 2 when either fails. Exit code 2 sends the hook’s stderr back to the agent instead of ending the turn, so the command redirects the build output to stderr:

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && { npx tsc --noEmit && npx next build; } 1>&2 || exit 2",
"timeout": 600
}
]
}
]
}
}

Stop takes no matcher. To stop a failing build from looping, check stop_hook_active in a script instead; see hooks automation. In Codex and Cursor, put the “Done means” line from the rules block in AGENTS.md or your Rules and require the command output in the summary. The developer who owns the pull request signs off on the gate results and the four files above; CI enforces the rest. For how that review works at team scale, see reviewing agent pull requests.

SymptomCauseRecovery
Whole page is client-rendered; generateMetadata has no effect'use client' on page.tsx or layout.tsxMove the directive to the smallest interactive leaf; add an ESLint rule or grep in CI that fails on 'use client' in page.tsx and layout.tsx
Build warns that middleware is deprecated, or fails with a runtime config error in proxy.tsAgent wrote Next.js 15 codenpx @next/codemod@canary middleware-to-proxy ., delete the runtime export, and confirm AGENTS.md still has the managed block
A mutation shows stale datarevalidateTag with a profile where the user needs to see their own writeUse updateTag inside the Server Action; keep revalidateTag(tag, 'max') for webhooks
Admin action works for a normal user through a crafted POSTAuthorization only in proxy.tsAdd requireRole() inside the action; add the direct-POST Playwright test from the auth recipe
Server Action or page fails with an “Only plain objects” serialization errorA class instance (an ORM entity, a Decimal) or a function crossed the server–client boundaryMap results to plain objects in the data layer before they reach a Client Component or leave an action; add a unit test that asserts the return value is a plain object
CSS or images stop loading after a Proxy changeMatcher runs on _next/static and _next/imageRestore the negative matcher; add a Playwright check that a page’s stylesheet returns 200
Agent keeps producing Next.js 14 code in a new sessionAGENTS.md block deleted, or the docs are not bundled (16.1 or earlier)Run next dev from the agent’s session (or npx @next/codemod@canary agents-md on 16.1 and earlier) to restore the block, then commit it

Did this guide help you?

One tap, no sign-in.

not at all a lot