Next.js dce-edge
install
source · Clone the upstream repo
git clone https://github.com/vercel/next.js
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/vercel/next.js "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.agents/skills/dce-edge" ~/.claude/skills/vercel-next-js-dce-edge && rm -rf "$T"
manifest:
.agents/skills/dce-edge/SKILL.mdsource content
DCE + Edge
Use this skill when changing conditional
require() paths, Node-only imports, or edge/runtime branching.
DCE-Safe require()
Pattern
require()Webpack only DCEs a
require() when it sits inside the dead branch of an if/else whose condition DefinePlugin can evaluate at compile time.
// CORRECT - webpack can eliminate the dead branch if (process.env.__NEXT_USE_NODE_STREAMS) { require('node:stream') } else { // web path }
What does NOT work:
- Early-return/throw guards: webpack doesn't do control-flow analysis for throws/returns, so the
is still traced.require() - Bare
withoutif
: works for inlineelse
specifiers but NOT fornode:*
that pulls a new file into the module graph.require('./some-module')
Always test edge changes with
pnpm test-start-webpack on test/e2e/app-dir/app/standalone.test.ts (has edge routes), not with NEXT_SKIP_ISOLATE=1 which skips the full webpack compilation.
TypeScript + DCE Interaction
Use
if/else (not two independent if blocks) when assigning a variable conditionally on process.env.X. TypeScript cannot prove exhaustiveness across if (flag) { x = a }; if (!flag) { x = b } and will error with "variable used before being assigned". The if/else pattern satisfies both TypeScript (definite assignment) and webpack DCE.
Compile-Time Switcher Pattern
Platform-specific code (node vs web) can use a single
.ts switcher module that conditionally require()s either .node.ts or .web.ts into a typed variable, then re-exports the shared runtime API as named exports. Keep the branch as if/else so DefinePlugin can dead-code-eliminate the unused require(). Keep shared types canonical in .node.ts, with .web.ts importing them via import type and the switcher re-exporting types as needed. Examples: stream-ops.ts and debug-channel-server.ts.
NEXT_RUNTIME
Is Not a Feature Flag
NEXT_RUNTIMEIn user-project webpack server compilers,
process.env.NEXT_RUNTIME is inlined to 'nodejs'. Guarding Node-only require('node:*') paths with NEXT_RUNTIME === 'nodejs' does not prune anything. For feature-gated codepaths, guard on the real feature define (e.g. process.env.__NEXT_USE_NODE_STREAMS).
Edge Runtime Constraints
Edge routes do NOT use pre-compiled runtime bundles. They are compiled by the user's webpack/Turbopack, so
define-env.ts controls DCE. Feature flags that gate node:* imports must be forced to false for edge builds in define-env.ts (isEdgeServer ? false : flagValue), otherwise webpack will try to resolve node:stream etc. and fail.
app-page.ts
Template Gotchas
app-page.ts
is a build template compiled by the user's bundler. Anyapp-page.ts
in this file is traced by webpack/turbopack atrequire()
time. You cannot require internal modules with relative paths because they won't be resolvable from the user's project. Instead, export new helpers fromnext build
and access them viaentry-base.ts
in the template.entryBase.*- Template helpers should stay out of
. IfRenderResult
needs a Node-stream-only utility, prefer a small dedicated helper module inapp-page.ts
(with DCE-safeserver/stream-utils/
+if/else
).require()
Verification
- Validate edge bundling regressions with
pnpm test-start-webpack test/e2e/app-dir/app/standalone.test.ts - For module-resolution/build-graph fixes, verify without
NEXT_SKIP_ISOLATE=1
Related Skills
- flag wiring (config/schema/define-env/runtime env)$flags
- entry-base boundaries and vendored React$react-vendoring
- reproduction and verification workflow$runtime-debug