home / skills / vercel / next.js / react-vendoring

react-vendoring skill

/.agents/skills/react-vendoring

This skill assists in vendoring React and enforcing entry-base.ts boundaries for react-server workflows across Next.js apps.

npx playbooks add skill vercel/next.js --skill react-vendoring

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

Files (1)
SKILL.md
3.8 KB
---
name: react-vendoring
description: >
  React vendoring and react-server layer boundaries. Use when editing
  entry-base.ts, $$compiled.internal.d.ts, compiled/react* packages,
  or taskfile.js copy_vendor_react. Covers the entry-base.ts boundary
  (all react-server-dom-webpack/* imports must go through it), vendored
  React channels, type declarations, Turbopack remap to
  react-server-dom-turbopack, ComponentMod access patterns, and ESLint
  suppression for guarded requires.
---

# React Vendoring

Use this skill for changes touching vendored React, `react-server-dom-webpack/*`, or react-server layer boundaries.

## App Router Vendoring

React is NOT resolved from `node_modules` for App Router. It's vendored into `packages/next/src/compiled/` during `pnpm build` (task: `copy_vendor_react()` in `taskfile.js`). Pages Router resolves React from `node_modules` normally.

- **Two channels**: stable (`compiled/react/`) and experimental (`compiled/react-experimental/`). The runtime bundle webpack config aliases to the correct channel via `makeAppAliases({ experimental })`.

## `entry-base.ts` Boundary

Only `entry-base.ts` is compiled in rspack's `(react-server)` layer. ALL imports from `react-server-dom-webpack/*` (Flight server/static APIs) must go through `entry-base.ts`. Other files like `stream-ops.node.ts` or `app-render.tsx` must access Flight APIs via the `ComponentMod` parameter (which is the `entry-base.ts` module exposed through the `app-page.ts` build template).

Direct imports from `react-server-dom-webpack/server.node` or `react-server-dom-webpack/static` in files outside `entry-base.ts` will fail at runtime with "The react-server condition must be enabled". Dev mode may mask this error, but production workers fail immediately.

## Type Declarations

`packages/next/types/$$compiled.internal.d.ts` contains `declare module` blocks for vendored React packages. When adding new APIs (e.g. `renderToPipeableStream`, `prerenderToNodeStream`), you must add type declarations here. The bare specifier types (e.g. `declare module 'react-server-dom-webpack/server'`) are what source code in `src/` imports against.

## Adding Node.js-Only React APIs

These exist in `.node` builds but not in the type definitions. Steps:

1. Add type declarations to `$$compiled.internal.d.ts`.
2. Export the API from `entry-base.ts` behind a `process.env` guard.
3. Access it via `ComponentMod` in other files.

```typescript
// In entry-base.ts (react-server layer) only:
/* eslint-disable import/no-extraneous-dependencies */
export let renderToPipeableStream: ... | undefined
if (process.env.__NEXT_USE_NODE_STREAMS) {
  renderToPipeableStream = (
    require('react-server-dom-webpack/server.node') as typeof import('react-server-dom-webpack/server.node')
  ).renderToPipeableStream
} else {
  renderToPipeableStream = undefined
}
/* eslint-enable import/no-extraneous-dependencies */

// In other files, access via ComponentMod:
ComponentMod.renderToPipeableStream!(payload, clientModules, opts)
```

## ESLint Practical Rule

For guarded runtime `require()` blocks that need `import/no-extraneous-dependencies` suppression, prefer scoped block disable/enable. If using `eslint-disable-next-line`, the comment must be on the line immediately before the `require()` call, NOT before the `const` declaration. When the `const` and `require()` are on different lines, this is error-prone.

## Turbopack Remap

`react-server-dom-webpack/*` is silently remapped to `react-server-dom-turbopack/*` by Turbopack's import map. Code says "webpack" everywhere, but Turbopack gets its own bindings at runtime. This affects debugging: stack traces and error messages will reference the turbopack variant.

## Related Skills

- `$flags` - flag wiring (config/schema/define-env/runtime env)
- `$dce-edge` - DCE-safe require patterns and edge constraints
- `$runtime-debug` - reproduction and verification workflow

Overview

This skill documents React vendoring and react-server layer boundaries for changes that touch vendored React, react-server-dom-webpack/*, or entry points like entry-base.ts. It explains the App Router vendoring channels, the strict entry-base.ts boundary, type declaration requirements, guarded runtime requires, and Turbopack remapping. Use it to avoid runtime failures and to keep runtime-only APIs and types correctly wired.

How this skill works

The App Router uses vendored React under packages/next/src/compiled/ with two channels (stable and experimental) chosen via build aliases. Only entry-base.ts is compiled into the react-server layer; all react-server-dom-webpack/* imports must be exposed through that module. Runtime-only Node APIs are conditionally required inside entry-base.ts behind env guards and then exposed through the ComponentMod parameter for other files to consume. Type declarations for vendored specifiers live in packages/next/types/$$compiled.internal.d.ts and must be updated when adding APIs.

When to use it

  • Editing entry-base.ts or any file that touches react-server-dom-webpack APIs
  • Adding or exposing node-only React APIs (e.g., stream APIs) to the server layer
  • Updating or adding vendored React packages under packages/next/src/compiled/
  • Modifying taskfile.js copy_vendor_react() or build aliases that choose stable/experimental channels
  • Fixing runtime errors like 'The react-server condition must be enabled' in production workers

Best practices

  • Keep all direct imports of react-server-dom-webpack/* inside entry-base.ts only
  • Expose runtime-only APIs via guarded require() in entry-base.ts and forward via ComponentMod
  • Add corresponding declare module blocks to $$compiled.internal.d.ts for any new specifier
  • Scope ESLint import/no-extraneous-dependencies disables to the exact guarded require line
  • Remember Turbopack remaps webpack specifiers to react-server-dom-turbopack at runtime

Example use cases

  • Adding renderToPipeableStream: declare types, require it in entry-base.ts under an env guard, then call via ComponentMod
  • Switching a page to experimental React channel: update makeAppAliases({ experimental }) and ensure compiled/react-experimental is populated
  • Fixing a production worker crash by moving a direct react-server-dom-webpack import into entry-base.ts
  • Adding a new vendored package: add files under compiled/, add declare module entries, and wire exports through entry-base.ts

FAQ

What happens if I import react-server-dom-webpack/* outside entry-base.ts?

It will usually fail at runtime with 'The react-server condition must be enabled' in production. Dev mode may mask the error but production workers will fail immediately.

Where do I add type declarations for vendored React APIs?

Add declare module blocks to packages/next/types/$$compiled.internal.d.ts for the bare specifier names your source imports (e.g., 'react-server-dom-webpack/server').