home / skills / vercel / next.js / flags

flags skill

/.agents/skills/flags

This skill guides adding or modifying Next.js experimental feature flags end-to-end, covering type declarations, zod schemas, build-time injection, and runtime

npx playbooks add skill vercel/next.js --skill flags

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
2.9 KB
---
name: flags
description: >
  How to add or modify Next.js experimental feature flags end-to-end.
  Use when editing config-shared.ts, config-schema.ts, define-env-plugin.ts,
  next-server.ts, export/worker.ts, or module.compiled.js. Covers type
  declaration, zod schema, build-time injection, runtime env plumbing,
  and the decision between runtime env-var branching vs separate bundle variants.
---

# Feature Flags

Use this skill when adding or changing framework feature flags in Next.js internals.

## Required Wiring

All flags need: `config-shared.ts` (type) → `config-schema.ts` (zod). If the flag is consumed in user-bundled code (client components, edge routes, `app-page.ts` template), also add it to `define-env.ts` for build-time injection. Runtime-only flags consumed exclusively in pre-compiled bundles can skip `define-env.ts`.

## Where the Flag Is Consumed

**Client/bundled code only** (e.g. `__NEXT_PPR` in client components): `define-env.ts` is sufficient. Webpack/Turbopack replaces `process.env.X` at the user's build time.

**Pre-compiled runtime bundles** (e.g. code in `app-render.tsx`): The flag must also be set as a real `process.env` var at runtime, because `app-render.tsx` runs from pre-compiled bundles where `define-env.ts` doesn't reach. Two approaches:

- **Runtime env var**: Set in `next-server.ts` + `export/worker.ts`. Both code paths stay in one bundle. Simple but increases bundle size.
- **Separate bundle variant**: Add DefinePlugin entry in `next-runtime.webpack-config.js` (scoped to `bundleType === 'app'`), new taskfile tasks, update `module.compiled.js` selector, and still set env var in `next-server.ts` + `export/worker.ts` for bundle selection. Eliminates dead code but adds build complexity.

For runtime flags, also add the field to the `NextConfigRuntime` Pick type in `config-shared.ts`.

## Runtime-Bundle Model

- Runtime bundles are built by `next-runtime.webpack-config.js` (rspack) via `taskfile.js` bundle tasks.
- Bundle selection occurs at runtime in `src/server/route-modules/app-page/module.compiled.js` based on `process.env` vars.
- Variants: `{turbo/webpack} × {experimental/stable/nodestreams/experimental-nodestreams} × {dev/prod}` = up to 16 bundles per route type.
- `define-env.ts` affects user bundling, not pre-compiled runtime internals.
- `process.env.X` checks in `app-render.tsx` are either replaced by DefinePlugin at runtime-bundle-build time, or read as actual env vars at server startup. They are NOT affected by the user's defines from `define-env.ts`.
- **Gotcha**: DefinePlugin entries in `next-runtime.webpack-config.js` must be scoped to the correct `bundleType` (e.g. `app` only, not `server`) to avoid replacing assignment targets in `next-server.ts`.

## Related Skills

- `$dce-edge` - DCE-safe require patterns and edge constraints
- `$react-vendoring` - entry-base boundaries and vendored React
- `$runtime-debug` - reproduction and verification workflow

Overview

This skill explains how to add or modify Next.js experimental feature flags end-to-end inside the framework. It covers type declaration, zod schema wiring, build-time injection for user bundles, runtime env plumbing for pre-compiled runtime bundles, and the trade-offs between runtime env-var branching and separate bundle variants. Use it when changing files like config-shared.ts, config-schema.ts, define-env-plugin.ts, next-server.ts, export/worker.ts, or module.compiled.js. The guidance targets maintainable, predictable flag behavior across build and runtime surfaces.

How this skill works

Every flag must be declared in config-shared.ts (type) and validated in config-schema.ts (zod). If the flag will be read from user-bundled code (client components, edge routes, app templates), add it to define-env.ts so the bundler injects a build-time replacement. Flags consumed by pre-compiled runtime bundles require actual process.env values at server startup or separate runtime-bundle variants built with DefinePlugin and selected via module.compiled.js.

When to use it

  • Adding a new experimental flag exposed to app or client code
  • Changing an existing flag used in both user bundles and pre-compiled runtime code
  • Deciding whether to eliminate dead code via separate runtime bundle variants
  • Wiring a runtime-only flag that must affect server-side precompiled bundles
  • Validating flag values and types across config-shared.ts and config-schema.ts

Best practices

  • Always add the flag type to config-shared.ts and the zod entry in config-schema.ts
  • If user code will import the flag, add it to define-env.ts for build-time injection
  • For flags used in pre-compiled runtime code, ensure process.env is set in next-server.ts and export/worker.ts
  • Prefer separate bundle variants only when dead-code elimination yields meaningful size savings—accept added build complexity otherwise
  • Scope DefinePlugin entries in next-runtime.webpack-config.js to the correct bundleType to avoid accidental replacements
  • Add runtime flags to NextConfigRuntime Pick in config-shared.ts so types reflect runtime behavior

Example use cases

  • Expose an experimental client rendering toggle consumed by client components and edge routes
  • Add a runtime-only optimization flag used inside app-render.tsx that must be read from process.env
  • Create separate app runtime bundles to remove server-only branches from client-side route modules
  • Fix a bug where a DefinePlugin entry unintentionally replaced an assignment in next-server.ts by scoping bundleType
  • Validate and default a new boolean flag through zod to prevent misconfiguration in user next.config.js

FAQ

Do I always need to add the flag to define-env.ts?

No. Only add it when user-bundled code (client components, edge routes, app templates) reads the flag. Runtime-only flags used in pre-compiled bundles can skip define-env.ts.

When should I create separate runtime bundle variants instead of using runtime env checks?

Create separate variants when eliminating dead code materially reduces bundle size or runtime cost. For small savings, prefer a single bundle with runtime env branching for simplicity.