Note:
CLAUDE.mdis a symlink toAGENTS.md. They are the same file.
This is a pnpm monorepo containing the Next.js framework and related packages.
next.js/
├── packages/ # Published npm packages
├── turbopack/ # Turbopack bundler (Rust) - git subtree
├── crates/ # Rust crates for Next.js SWC bindings
├── test/ # All test suites
├── examples/ # Example Next.js applications
├── docs/ # Documentation
└── scripts/ # Build and maintenance scripts
The main Next.js framework lives in packages/next/. This is what gets published as the next npm package.
Source code is in packages/next/src/.
Key entry points:
- Dev server:
src/cli/next-dev.ts→src/server/dev/next-dev-server.ts - Production server:
src/cli/next-start.ts→src/server/next-server.ts - Build:
src/cli/next-build.ts→src/build/index.ts
Compiled output goes to packages/next/dist/ (mirrors src/ structure).
packages/create-next-app/- Thecreate-next-appCLI toolpackages/next-swc/- Native Rust bindings (SWC transforms)packages/eslint-plugin-next/- ESLint rules for Next.jspackages/font/-next/fontimplementationpackages/third-parties/- Third-party script integrations
Before editing or creating files in any subdirectory (e.g., packages/*, crates/*), read all README.md files in the directory path from the repo root up to and including the target file's directory. This helps identify any local patterns, conventions, and documentation.
Example: Before editing turbopack/crates/turbopack-ecmascript-runtime/js/src/nodejs/runtime/runtime-base.ts, read:
turbopack/README.md(if exists)turbopack/crates/README.md(if exists)turbopack/crates/turbopack-ecmascript-runtime/README.md(if exists)turbopack/crates/turbopack-ecmascript-runtime/js/README.md(if exists - closest to target file)
# Build the Next.js package
pnpm --filter=next build
# Build everything
pnpm build
# Run specific task
pnpm --filter=next exec taskr <task>For iterative development, default to watch mode + skip-isolate for the inner loop (not full builds), with exceptions noted below.
Default agent rule: If you are changing Next.js source or integration tests, start pnpm --filter=next dev in a separate terminal session before making edits (unless it is already running). If you skip this, explicitly state why (for example: docs-only, read-only investigation, or CI-only analysis).
1. Start watch build in background:
# Auto-rebuilds on file changes (~1-2s per change vs ~60s full build)
# Keep this running while you iterate on code
pnpm --filter=next dev2. Run tests fast (no isolation, no packing):
# NEXT_SKIP_ISOLATE=1 - skip packing Next.js for each test (~100s faster)
# testonly - runs with --runInBand (no worker isolation overhead)
NEXT_SKIP_ISOLATE=1 NEXT_TEST_MODE=dev pnpm testonly test/path/to/test.ts3. When done, kill the background watch process (if you started it).
For type errors only: Use pnpm --filter=next types (~10s) instead of pnpm --filter=next build (~60s).
After the workspace is bootstrapped, prefer pnpm --filter=next build when edits are limited to core Next.js files. Use full pnpm build for branch switches/bootstrap, before CI push, or when changes span multiple packages.
Always run a full bootstrap build after switching branches:
git checkout <branch>
pnpm build # Sets up outputs for dependent packages (Turborepo dedupes if unchanged)When NOT to use NEXT_SKIP_ISOLATE: Drop it when testing module resolution changes (new require() paths, new exports from entry-base.ts, edge route imports). Without isolation, the test uses local dist/ directly, hiding resolution failures that occur when Next.js is packed as a real npm package.
Turbopack is the default bundler for both next dev and next build. To force webpack:
next build --webpack # Production build with webpack
next dev --webpack # Dev server with webpackThere is no --no-turbopack flag.
# Run specific test file (development mode with Turbopack)
pnpm test-dev-turbo test/path/to/test.test.ts
# Run tests matching pattern
pnpm test-dev-turbo -t "pattern"
# Run development tests
pnpm test-dev-turbo test/development/Test commands by mode:
pnpm test-dev-turbo- Development mode with Turbopack (default)pnpm test-dev-webpack- Development mode with Webpackpnpm test-start-turbo- Production build+start with Turbopackpnpm test-start-webpack- Production build+start with Webpack
Other test commands:
pnpm test-unit- Run unit tests only (fast, no browser)pnpm testonly <path>- Run tests without rebuilding (faster iteration when build artifacts are already up to date)pnpm new-test- Generate a new test file from template (interactive)
Generate tests non-interactively (for AI agents):
Generating tests using pnpm new-test is mandatory.
# Use --args for non-interactive mode
# Format: pnpm new-test --args <appDir> <name> <type>
# appDir: true/false (is this for app directory?)
# name: test name (e.g. "my-feature")
# type: e2e | production | development | unit
pnpm new-test --args true my-feature e2eAnalyzing test output efficiently:
Never re-run the same test suite with different grep filters. Capture output once to a file, then read from it:
# Run once, save everything
HEADLESS=true pnpm test-dev-turbo test/path/to/test.ts > /tmp/test-output.log 2>&1
# Then analyze without re-running
grep "●" /tmp/test-output.log # Failed test names
grep -A5 "Error:" /tmp/test-output.log # Error details
tail -5 /tmp/test-output.log # SummaryTest writing expectations:
-
Use
pnpm new-testto generate new test suites - it creates proper structure with fixture files -
Use
retry()fromnext-test-utilsinstead ofsetTimeoutfor waiting// Good - use retry() for polling/waiting import { retry } from 'next-test-utils' await retry(async () => { const text = await browser.elementByCss('p').text() expect(text).toBe('expected value') }) // Bad - don't use setTimeout for waiting await new Promise((resolve) => setTimeout(resolve, 1000))
-
Do NOT use
check()- it is deprecated. Useretry()+expect()instead// Deprecated - don't use check() await check(() => browser.elementByCss('p').text(), /expected/) // Good - use retry() with expect() await retry(async () => { const text = await browser.elementByCss('p').text() expect(text).toMatch(/expected/) })
-
Prefer real fixture directories over inline
filesobjects// Good - use a real directory with fixture files const { next } = nextTestSetup({ files: __dirname, // points to directory containing test fixtures }) // Avoid - inline file definitions are harder to maintain const { next } = nextTestSetup({ files: { 'app/page.tsx': `export default function Page() { ... }`, }, })
pnpm lint # Full lint (types, prettier, eslint, ast-grep)
pnpm lint-fix # Auto-fix lint issues
pnpm prettier-fix # Fix formatting only
pnpm types # TypeScript type checkingWhen the user asks about CI failures, PR reviews, or the status of a PR, run the pr-status script:
node scripts/pr-status.js # Auto-detects PR from current branch
node scripts/pr-status.js <number> # Analyze specific PR by numberThis generates analysis files in scripts/pr-status/.
General triage rules (always apply; $pr-status-triage skill expands on these):
- Prioritize blocking failures first: build, lint, types, then tests.
- Assume failures are real until disproven; use "Known Flaky Tests" as context, not auto-dismissal.
- Reproduce with the same CI mode/env vars (especially
IS_WEBPACK_TEST=1when present). - For module-resolution/build-graph fixes, verify without
NEXT_SKIP_ISOLATE=1.
For full triage workflow (failure prioritization, mode selection, CI env reproduction, and common failure patterns), use the $pr-status-triage skill:
- Skill file:
.agents/skills/pr-status-triage/SKILL.md
Use /pr-status for automated analysis - analyzes failing jobs and review comments in parallel, groups failures by test file.
CI Analysis Tips:
- Prioritize CI failures over review comments
- Prioritize blocking jobs first: build, lint, types, then test jobs
- Common fast checks:
rust check / build→ Runcargo fmt -- --check, thencargo fmtlint / build→ Runpnpm prettier --write <file>for prettier errors- test failures → Run the specific failing test path locally
Run tests in the right mode:
# Dev mode (Turbopack)
pnpm test-dev-turbo test/path/to/test.ts
# Prod mode
pnpm test-start-turbo test/path/to/test.tsSee Codebase structure above for detailed explanations.
packages/next/src/- Main Next.js source codepackages/next/src/server/- Server runtime (most changes happen here)packages/next/src/client/- Client-side runtimepackages/next/src/build/- Build toolingtest/e2e/- End-to-end teststest/development/- Dev server teststest/production/- Production build teststest/unit/- Unit tests (fast, no browser)
- The dev server entry point is
packages/next/src/cli/next-dev.ts - Router server:
packages/next/src/server/lib/router-server.ts - Use
DEBUG=next:*for debug logging - Use
NEXT_TELEMETRY_DISABLED=1when testing locally
Both next dev and next build --debug-prerender produce bundles with NODE_ENV=development. Use process.env.__NEXT_DEV_SERVER to distinguish between them:
process.env.NODE_ENV !== 'production'— code that should exist in dev bundles but be eliminated from prod bundles. This is a build-time check.process.env.__NEXT_DEV_SERVER— code that should only run with the dev server (next dev), not duringnext build --debug-prerenderornext start.
Always treat environment variable values as sensitive unless they are known test-mode flags.
- Never print or paste secret values (tokens, API keys, cookies) in chat responses, commits, or shared logs.
- Mirror CI env names and modes exactly, but do not inline literal secret values in commands.
- If a required secret is missing locally, stop and ask the user rather than inventing placeholder credentials.
- Never commit local secret files; if documenting env setup, use placeholder-only examples.
- When sharing command output, summarize and redact sensitive-looking values.
Use skills for conditional, deep workflows. Keep baseline iteration/build/test policy in this file.
$pr-status-triage- CI failure and PR review triage withscripts/pr-status.js$flags- feature-flag wiring across config/schema/define-env/runtime env$dce-edge- DCE-saferequire()patterns and edge/runtime constraints$react-vendoring-entry-base.tsboundaries and vendored React type/runtime rules$runtime-debug- runtime-bundle/module-resolution regression reproduction and verification$authoring-skills- how to create and maintain skills in.agents/skills/
Reading large files (>500 lines, e.g. app-render.tsx):
- Grep first to find relevant line numbers, then read targeted ranges with
offset/limit - Never re-read the same section of a file without code changes in between
- For generated files (
dist/,node_modules/,.next/): search only, don't read
Build & test output:
- Capture to file once, then analyze:
pnpm build 2>&1 | tee /tmp/build.log - Don't re-run the same test command without code changes; re-analyze saved output instead
Batch edits before building:
- Group related edits across files, then run one build, not build-per-edit
- Use
pnpm --filter=next types(~10s) to check type errors without full rebuild
External API calls (gh, curl):
- Save response to variable or file:
JOBS=$(gh api ...) && echo "$JOBS" | jq '...' - Don't re-fetch the same API data to analyze from different angles
- Do NOT add "Generated with Claude Code" or co-author footers to commits or PRs
- Keep commit messages concise and descriptive
- PR descriptions should focus on what changed and why
- Do NOT mark PRs as "ready for review" (
gh pr ready) - leave PRs in draft mode and let the user decide when to mark them ready
- Split work into smaller, individually verifiable tasks. Before starting, break the overall goal into incremental steps where each step produces a result that can be checked independently.
- Verify each task before moving on to the next. After completing a step, confirm it works correctly (e.g., run relevant tests, check types, build, or manually inspect output). Do not proceed to the next task until the current one is verified.
- Choose the right verification method for each change. This may include running unit tests, integration tests, type checking, linting, building the project, or inspecting runtime behavior depending on what was changed.
- When unclear how to verify a change, ask the user. If there is no obvious test or verification method for a particular change, ask the user how they would like it verified before moving on.
Pre-validate before committing to avoid slow lint-staged failures (~2 min each):
# Run exactly what the pre-commit hook runs on your changed files:
pnpm prettier --with-node-modules --ignore-path .prettierignore --write <files>
npx eslint --config eslint.config.mjs --fix <files>When running Next.js integration tests, you must rebuild if source files have changed:
- First run after branch switch/bootstrap (or if unsure)? →
pnpm build - Edited only core Next.js files (
packages/next/**) after bootstrap? →pnpm --filter=next build - Edited Next.js code or Turbopack (Rust)? →
pnpm build
For runtime internals, use focused skills:
- Feature-flag plumbing and runtime bundle wiring:
$flags(.agents/skills/flags/SKILL.md) - DCE and edge/runtime constraints:
$dce-edge(.agents/skills/dce-edge/SKILL.md) - React vendoring and
entry-base.tsboundaries:$react-vendoring(.agents/skills/react-vendoring/SKILL.md) - Debugging and verification workflow:
$runtime-debug(.agents/skills/runtime-debug/SKILL.md)
Keep these high-frequency guardrails in mind:
- Reproduce module resolution and bundling issues without
NEXT_SKIP_ISOLATE=1 - Validate edge bundling regressions with
pnpm test-start-webpack test/e2e/app-dir/app/standalone.test.ts - Use
__NEXT_SHOW_IGNORE_LISTED=truewhen you need full internal stack traces
Core runtime/bundling rules (always apply; skills above expand on these with verification steps and examples):
- New flags: add type in
config-shared.ts, schema inconfig-schema.ts, anddefine-env.tswhen used in user-bundled code. - If a flag is consumed in pre-compiled runtime internals, also wire runtime env values (
next-server.ts/export/worker.tsas needed). define-env.tsaffects user bundling; it does not control pre-compiled runtime bundle internals.- Keep
require()behind compile-timeif/elsebranches for DCE (avoid early-return/throw patterns). - In edge builds, force feature flags that gate Node-only imports to
falseindefine-env.ts. react-server-dom-webpack/*imports must stay inentry-base.ts; consume via component module exports elsewhere.
- Cache components enables PPR by default: When
__NEXT_CACHE_COMPONENTS=true, most app-dir pages use PPR implicitly. Dedicatedppr-full/andppr/test suites are mostlydescribe.skip(migrating to cache components). To test PPR codepaths, run normal app-dir e2e tests with__NEXT_CACHE_COMPONENTS=truerather than looking for explicit PPR test suites. - Quick smoke testing with toy apps: For fast feedback, generate a minimal test fixture with
pnpm new-test --args true <name> e2e, then run the dev server directly withnode packages/next/dist/bin/next dev --port <port>andcurl --max-time 10. This avoids the overhead of the full test harness and gives immediate feedback on hangs/crashes. - Mode-specific tests need
skipStart: true+ manualnext.start()inbeforeAllafter mode check - Don't rely on exact log messages - filter by content patterns, find sequences not positions
- Snapshot tests vary by env flags: Tests with inline snapshots can produce different output depending on env flags. When updating snapshots, always run the test with the exact env flags the CI job uses (check
.github/workflows/build_and_test.ymlafterBuild:sections). Turbopack resolvesreact-dom/server.edge(no Node APIs likerenderToPipeableStream), while webpack resolves the.nodebuild (has them). app-page.tsis a build template compiled by the user's bundler: Anyrequire()in this file is traced by webpack/turbopack atnext buildtime. You cannot require internal modules with relative paths because they won't be resolvable from the user's project. Instead, export new helpers fromentry-base.tsand access them viaentryBase.*in the template.- Reproducing CI failures locally: Always match the exact CI env vars (check
pr-statusoutput for "Job Environment Variables"). Key differences:IS_WEBPACK_TEST=1forces webpack (turbopack is default),NEXT_SKIP_ISOLATE=1skips packing next.js (hides module resolution failures). Always run withoutNEXT_SKIP_ISOLATEwhen verifying module resolution fixes. - Showing full stack traces: Set
__NEXT_SHOW_IGNORE_LISTED=trueto disable the ignore-list filtering in dev server error output. By default, Next.js collapses internal frames toat ignore-listed frames, which hides useful context when debugging framework internals. Defined inpackages/next/src/server/patch-error-inspect.ts.
- cargo fmt uses ASCII order (uppercase before lowercase) - just run
cargo fmt - Internal compiler error (ICE)? Delete incremental compilation artifacts and retry. Remove
*/incrementaldirectories from your cargo target directory (defaulttarget/, or checkCARGO_TARGET_DIRenv var)
findSourceMap()needs--enable-source-mapsflag or returns undefined- Source map paths vary (webpack:
./src/, tsc:src/) - try multiple formats process.cwd()in stack trace formatting produces different paths in tests vs production
If Turbopack produces unexpected errors after switching branches or pulling, check if packages/next-swc/native/*.node is stale. Delete it and run pnpm install to get the npm-published binary instead of a locally-built one.
- When adding
highlight={...}attributes to code blocks, carefully count the actual line numbers within the code block - Account for empty lines, import statements, and type imports that shift line numbers
- Highlights should point to the actual relevant code, not unrelated lines like
return (or framework boilerplate - Double-check highlights by counting lines from 1 within each code block