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, use watch mode + fast test execution:
1. Start watch build in background:
# Runs taskr in watch mode - auto-rebuilds on file changes
# Use Bash(run_in_background=true) to keep working while it runs
pnpm --filter=next dev2. Run tests fast (no isolation, no packing):
# NEXT_SKIP_ISOLATE=1 - skip packing Next.js for each test (much 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.
Only use full pnpm --filter=next build for one-off builds (after branch switch, before CI push).
Always rebuild after switching branches:
git checkout <branch>
pnpm build # Required before running tests (Turborepo dedupes if unchanged)# 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)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 e2eTest 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 fetches CI workflow runs, failed jobs, logs, and PR review comments, generating markdown files in scripts/pr-status/.
Use /pr-status for automated analysis - analyzes failing jobs and review comments in parallel, groups failures by test file.
CI Analysis Tips:
- Prioritize blocking jobs first: build, lint, types, then test jobs
- Prioritize CI failures over review comments
Common failure patterns:
rust check / build→ Runcargo fmt -- --checklocally, fix withcargo fmtlint / build→ Runpnpm prettier --write <file>for prettier errors- Test failures → Run the specific test locally with
pnpm test-dev-turbo <test-path>
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
- 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.
When running Next.js integration tests, you must rebuild if source files have changed:
- Edited Next.js code? →
pnpm build - Edited Turbopack (Rust)? →
pnpm swc-build-native - Edited both? →
pnpm turbo build build-native
- 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
- 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
- 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