Open-Source Wikis

/

Next.js

/

How to contribute

/

Patterns and conventions

vercel/next.js

Patterns and conventions

The framework has a number of cross-cutting rules that protect correctness, especially around feature flags, edge bundling, React vendoring, and the App Router rendering boundary. Many of these are codified as agent skills under .agents/skills/.

Adding a new experimental flag

The flag plumbing is end-to-end: type, schema, build-time injection, runtime env. All four steps are usually required.

graph TD
    Type["1. Type<br/>config-shared.ts"] --> Schema["2. Zod schema<br/>config-schema.ts"]
    Schema --> Define["3. Build-time inject<br/>build/define-env.ts"]
    Define --> Runtime["4. Runtime env (if needed)<br/>next-server.ts<br/>export/worker.ts"]
Step File When
1 packages/next/src/server/config-shared.ts Always
2 packages/next/src/server/config-schema.ts Always (zod schema)
3 packages/next/src/build/define-env.ts If consumed in user-bundled code
4 packages/next/src/server/next-server.ts or packages/next/src/export/worker.ts If consumed in pre-compiled runtime internals

The distinction in step 3 vs 4 is critical: define-env.ts controls bundling for the user's code. The pre-compiled module.compiled.js runtime internals are not affected by define-env.ts and need separate plumbing.

For edge builds, force flags that gate Node-only imports to false in define-env.ts. Otherwise the dead-code branches won't be eliminated and edge bundles will fail to resolve node:stream and friends.

The flags agent skill covers this end-to-end with worked examples.

DCE-safe require() patterns

For features that conditionally import Node modules, guard with compile-time if/else branches, not early-return / throw patterns. webpack's DCE only eliminates dead code in then/else branches.

// Good — webpack DCE eliminates the require for edge bundles
let nodeStream: typeof import('node:stream') | undefined;
if (process.env.NEXT_RUNTIME !== 'edge') {
  nodeStream = require('node:stream');
}

// Bad — the require is still emitted in edge bundles
if (process.env.NEXT_RUNTIME === 'edge') {
  throw new Error('not supported on edge');
}
const nodeStream = require('node:stream');

TypeScript's definite-assignment assertions (!) are useful here. The dce-edge agent skill has the full pattern catalog.

NEXT_RUNTIME vs real feature flags

process.env.NEXT_RUNTIME is not a feature flag — it's a runtime constant injected per build. Use it only to guard runtime-specific imports. Do not put behavior changes behind it.

React vendoring rules

The framework vendors React into packages/next/src/compiled/react* and exposes the React Server runtime through a single boundary file: packages/next/src/server/app-render/entry-base.ts.

Rule Enforcement
All react-server-dom-webpack/* imports must live in entry-base.ts ESLint + agent skill react-vendoring
Other code accesses these via re-exports (entryBase.foo) Convention
Turbopack remaps react-server-dom-webpack to react-server-dom-turbopack crates/next-core/src/next_config.rs + taskfile.js copy_vendor_react
Vendored React types live in packages/next/src/$$compiled.internal.d.ts Manual

The react-vendoring agent skill covers the boundary in detail.

app-page.ts is a build template

packages/next/src/build/templates/app-page.ts is compiled by the user's bundler at next build time. Every require() inside this file is traced by webpack/turbopack against the user's project, not the framework's own paths. That means:

  • You cannot require internal modules with relative paths from this file — they will not resolve in the user's project.
  • New helpers must be exported from entry-base.ts and accessed via entryBase.*.

Cache components and PPR

When __NEXT_CACHE_COMPONENTS=true:

  • Most app-dir pages enable Partial Prerendering implicitly.
  • The dedicated ppr-full/ and ppr/ test suites are mostly describe.skip while the migration lands.
  • Test PPR codepaths through normal app-dir e2e tests with the flag set, not through the legacy PPR suites.

Server security: internal headers

Next.js strips internal headers from incoming requests via filterInternalHeaders() in packages/next/src/server/lib/server-ipc/utils.ts. This runs at the entry point in packages/next/src/server/lib/router-server.ts before any server code executes.

Convention for new code: if you read a request header that is not a standard HTTP header (content-type, accept, user-agent, host, authorization, cookie, etc.), audit whether it should be in the INTERNAL_HEADERS array. Otherwise it could be forged by an external attacker.

Secrets and env safety

  • Never print or paste secret values (tokens, API keys, cookies) in chat output, logs, or commits.
  • Mirror CI env names and modes exactly, but do not inline literal secret values in commands.
  • Never commit local secret files. Use placeholder examples in docs.
  • When sharing command output, redact sensitive-looking values.

Code style

Convention Tool / file
Prettier formatting .prettierrc.json + pnpm prettier-fix
ESLint eslint.config.mjs + pnpm lint-eslint
ast-grep checks sgconfig.yml + pnpm lint-ast-grep
Inclusive language .alexrc + pnpm lint-language
Rust formatting .rustfmt.toml + cargo fmt
TypeScript tsconfig.json + pnpm types

cargo fmt uses ASCII order (uppercase before lowercase). Don't second-guess it; just run cargo fmt.

Documentation code blocks

When adding highlight={...} attributes to docs code blocks under docs/:

  • Count actual line numbers within the code block (1-indexed from inside the block).
  • Account for empty lines, imports, and type imports that shift line numbers.
  • Highlights should point to the relevant code, not unrelated lines like return (.

Run with the update-docs agent skill if you're updating docs alongside framework changes.

Specialized skills

The repo has skill files at .agents/skills/<name>/SKILL.md for conditional, deep workflows. Each is loaded on-demand:

Skill Purpose
flags Adding/modifying experimental flags
dce-edge DCE-safe require patterns and edge constraints
react-vendoring entry-base.ts boundary and vendored React rules
runtime-debug Module-resolution and bundle-size regression debugging
pr-status-triage CI failure and PR review triage with scripts/pr-status.js
router-act Tests using createRouterAct and LinkAccordion
update-docs Updating docs/ for code changes
write-api-reference Generating API reference pages under docs/01-app/03-api-reference/
write-guide Writing guides under docs/01-app/02-guides/
authoring-skills How to create and maintain skills
v8-jit V8 JIT optimization patterns for hot-path code

When working in any of those areas, read the corresponding SKILL.md first.

Performance: V8 JIT

Server hot paths (per-request code in app-render, routing, caching, stream-utils) need to stay V8-friendly:

  • Keep object shapes monomorphic (always assign properties in the same order, prefer class-like shapes over dynamic objects).
  • Avoid megamorphic call sites — properties read on >4 different shapes deopt the call.
  • Don't allocate closures per-request inside hot loops; hoist them.
  • Keep arrays packed (no holes, no mixed types). delete arr[i] un-packs.

The v8-jit agent skill has profile/deopt guidance.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Next.js wiki | Factory