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.tsand accessed viaentryBase.*.
Cache components and PPR
When __NEXT_CACHE_COMPONENTS=true:
- Most app-dir pages enable Partial Prerendering implicitly.
- The dedicated
ppr-full/andppr/test suites are mostlydescribe.skipwhile 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.