Skip to content

Avoid proxying React modules through workUnitStore (#85486)#8

Open
MitchLewis930 wants to merge 1 commit intopr_038_beforefrom
pr_038_after
Open

Avoid proxying React modules through workUnitStore (#85486)#8
MitchLewis930 wants to merge 1 commit intopr_038_beforefrom
pr_038_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 30, 2026

PR_038

Summary by CodeRabbit

Release Notes

  • Refactor

    • Simplified internal error stack capture and React instance registration mechanisms for improved maintainability.
    • Removed unused properties from prerender data structures to reduce overhead.
  • Tests

    • Updated test infrastructure to reflect internal signal handling changes.

✏️ Tip: You can customize this high-level summary in your review settings.

Today the `captureOwnerStack()` function is provided to shared utilities
through an AsyncLocalStorage that scopes the method from the appropriate
React instance. This is so that external code like patches to sync IO
methods can still generate errors with the appropriate React owner
information even when the patched code itself is not bundled and can be
called from etiher SSR or RSC contexts.

This works but it makes plumbing the React instances around tricky.
There is a simpler way. Most of the time you can just try both React's.
If one gives you a non-null/undefined result then you know you are in
that scope. If neither do then you're outside a React scope altogether.

In this change I remove `captureOwnerStack()` from the workUnitStore
types and just call it from the shared server runtime which gives even
external code access to the appropriate React instances for bundled code
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

This pull request removes the captureOwnerStack property from prerender stores and replaces a local cache signal registration system with a centralized runtime-reacts module for registering and accessing client and server React instances. Console-dim patches now retrieve signals through the new runtime-reacts accessors instead of iterating over registered callbacks.

Changes

Cohort / File(s) Summary
Prerender Store Simplification
packages/next/src/server/app-render/work-unit-async-storage.external.ts, packages/next/src/server/app-render/app-render.tsx, packages/next/src/server/route-modules/app-route/module.ts
Removed captureOwnerStack property from PrerenderStoreModernCommon interface and eliminated destructuring/initialization of this field in prerender flow, reducing the shape of prerender data structures.
Runtime React Registration Module
packages/next/src/server/runtime-reacts.external.ts
New module that exports four public functions (registerClientReact, getClientReact, registerServerReact, getServerReact) to centralize storage and retrieval of client and server React module instances.
Console-Dim Signal Registration Refactor
packages/next/src/server/node-environment-extensions/console-dim.external.tsx, packages/next/src/server/node-environment-extensions/console-dim.external.test.ts
Replaced local registerGetCacheSignal registry with unified signal access via getClientReact()?.cacheSignal() ?? getServerReact()?.cacheSignal(), removing the per-signal iteration loop and simplifying dimming logic.
Owner Stack Capture Update
packages/next/src/server/node-environment-extensions/utils.tsx
Updated applyOwnerStack function signature from accepting workUnitStore parameter to standalone operation; now retrieves owner stack via getClientReact()?.captureOwnerStack() ?? getServerReact()?.captureOwnerStack().
Route Module React Registration
packages/next/src/server/route-modules/app-page/module.ts
Switched from registering console cacheSignal to registering client and server React instances via registerServerReact and registerClientReact with vendored Reacts.
NFT Snapshot Update
test/production/next-server-nft/next-server-nft.test.ts
Added new runtime file path node_modules/next/dist/server/runtime-reacts.external.js to NFT snapshot for proper dependency tracing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through refactored code,
No captureOwnerStack to bear the load,
Now React instances dance with glee,
Runtime-reacts sets them free,
Signals unified, sweet and bright,
The stack is lighter, the code is light!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete, providing only a placeholder 'PR_038' instead of following the template structure with What/Why/How sections and proper issue/PR links. Add a complete description following the template with What (changes made), Why (motivation), How (implementation approach), and link to the related issue/Linear ticket (Fixes NEXT-xxx and/or Fixes #85486).
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: removing React module proxying through workUnitStore and introducing a direct registry-based approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr_038_after

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@packages/next/src/server/node-environment-extensions/console-dim.external.tsx`:
- Around line 189-215: The code calls cacheSignal() unguarded which will throw
for React builds that don't implement it; update the signal assignment to call
cacheSignal via optional chaining on the method call (e.g.,
getClientReact()?.cacheSignal?.() ?? getServerReact()?.cacheSignal?.()) so that
when getClientReact() or getServerReact() lack cacheSignal the code falls back
to the existing workUnitStore logic; ensure this change preserves the subsequent
use of signal and does not alter the applyWithDimming/originalMethod branches
(symbols: getClientReact, getServerReact, cacheSignal, signal, applyWithDimming,
originalMethod, methodName, args, consoleStore).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e53071 and c0c75e4.

📒 Files selected for processing (9)
  • packages/next/src/server/app-render/app-render.tsx
  • packages/next/src/server/app-render/work-unit-async-storage.external.ts
  • packages/next/src/server/node-environment-extensions/console-dim.external.test.ts
  • packages/next/src/server/node-environment-extensions/console-dim.external.tsx
  • packages/next/src/server/node-environment-extensions/utils.tsx
  • packages/next/src/server/route-modules/app-page/module.ts
  • packages/next/src/server/route-modules/app-route/module.ts
  • packages/next/src/server/runtime-reacts.external.ts
  • test/production/next-server-nft/next-server-nft.test.ts
💤 Files with no reviewable changes (2)
  • packages/next/src/server/app-render/work-unit-async-storage.external.ts
  • packages/next/src/server/route-modules/app-route/module.ts
🧰 Additional context used
🧬 Code graph analysis (4)
packages/next/src/server/app-render/app-render.tsx (1)
packages/next/src/build/templates/edge-ssr-app.ts (1)
  • ComponentMod (53-53)
packages/next/src/server/node-environment-extensions/utils.tsx (1)
packages/next/src/server/runtime-reacts.external.ts (2)
  • getClientReact (5-7)
  • getServerReact (13-15)
packages/next/src/server/node-environment-extensions/console-dim.external.test.ts (1)
packages/next/src/server/runtime-reacts.external.ts (2)
  • registerServerReact (10-12)
  • registerClientReact (2-4)
packages/next/src/server/node-environment-extensions/console-dim.external.tsx (1)
packages/next/src/server/runtime-reacts.external.ts (2)
  • getClientReact (5-7)
  • getServerReact (13-15)
🔇 Additional comments (8)
test/production/next-server-nft/next-server-nft.test.ts (1)

324-324: Snapshot update for runtime-reacts looks correct.

packages/next/src/server/runtime-reacts.external.ts (1)

1-15: LGTM — minimal and clear React instance registry.

packages/next/src/server/route-modules/app-page/module.ts (1)

42-47: LGTM — Node runtime now registers the vendored React instances.

packages/next/src/server/app-render/app-render.tsx (1)

3316-3316: LGTM — local createElement binding is a clean refactor.

packages/next/src/server/node-environment-extensions/console-dim.external.test.ts (1)

324-340: LGTM — test now wires cacheSignal via runtime-reacts registry.

packages/next/src/server/node-environment-extensions/utils.tsx (2)

46-52: LGTM — updated applyOwnerStack callsites match new signature.

Also applies to: 78-83


103-134: LGTM — owner stack capture now uses runtime-reacts fallback.

packages/next/src/server/node-environment-extensions/console-dim.external.tsx (1)

7-7: LGTM — console-dim now sources React instances from runtime-reacts.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +189 to 215
const signal =
getClientReact()?.cacheSignal() ?? getServerReact()?.cacheSignal()
if (signal) {
// We are in a React Server render and can consult the React cache signal to determine if logs
// are now dimmable.
if (signal.aborted) {
if (currentAbortedLogsStyle === HIDDEN_STYLE) {
return
}
return applyWithDimming.call(
this,
consoleStore,
originalMethod,
methodName,
args
)
} else if (consoleStore?.dim === true) {
return applyWithDimming.call(
this,
consoleStore,
originalMethod,
methodName,
args
)
} else {
return originalMethod.apply(this, args)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

React cacheSignal API version introduced when stable

💡 Result:

cacheSignal was introduced as a stable React API in React 19.2.0, released October 1, 2025. [1], [2]

Stable version: react@19.2.0 (and later). [2], [3]

Docs: It’s documented in the React API reference under cacheSignal. [3]

Sources:
[1] React 19.2 release post (Oct 1, 2025) (react.dev)
[2] React changelog / release notes for 19.2.0 (collected.press)
[3] React API reference page for cacheSignal (react.dev)

Citations:


🏁 Script executed:

# First, let's examine the actual code file to understand the context
cat -n packages/next/src/server/node-environment-extensions/console-dim.external.tsx | head -250 | tail -100

Repository: Signal65/next.js-CodeRabbit

Length of output: 4802


🏁 Script executed:

# Search for getClientReact and getServerReact definitions
rg -n "getClientReact|getServerReact" packages/next/src/server/node-environment-extensions/console-dim.external.tsx -A 5

Repository: Signal65/next.js-CodeRabbit

Length of output: 691


🏁 Script executed:

# Check if there are any version checks or other guards in the file
rg -n "React\." packages/next/src/server/node-environment-extensions/console-dim.external.tsx | head -20

Repository: Signal65/next.js-CodeRabbit

Length of output: 179


Guard cacheSignal method call for React builds that don't implement it.

cacheSignal is only available as a stable API in React 19.2.0 (October 2025). Since Next.js must support older React versions (18.x and earlier 19.x releases), the code will throw when getClientReact() or getServerReact() return objects without the cacheSignal method. This prevents the fallback to workUnitStore from ever executing.

Use optional chaining on the method call itself:

🛠️ Required fix
-      const signal =
-        getClientReact()?.cacheSignal() ?? getServerReact()?.cacheSignal()
+      const signal =
+        getClientReact()?.cacheSignal?.() ?? getServerReact()?.cacheSignal?.()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const signal =
getClientReact()?.cacheSignal() ?? getServerReact()?.cacheSignal()
if (signal) {
// We are in a React Server render and can consult the React cache signal to determine if logs
// are now dimmable.
if (signal.aborted) {
if (currentAbortedLogsStyle === HIDDEN_STYLE) {
return
}
return applyWithDimming.call(
this,
consoleStore,
originalMethod,
methodName,
args
)
} else if (consoleStore?.dim === true) {
return applyWithDimming.call(
this,
consoleStore,
originalMethod,
methodName,
args
)
} else {
return originalMethod.apply(this, args)
}
const signal =
getClientReact()?.cacheSignal?.() ?? getServerReact()?.cacheSignal?.()
if (signal) {
// We are in a React Server render and can consult the React cache signal to determine if logs
// are now dimmable.
if (signal.aborted) {
if (currentAbortedLogsStyle === HIDDEN_STYLE) {
return
}
return applyWithDimming.call(
this,
consoleStore,
originalMethod,
methodName,
args
)
} else if (consoleStore?.dim === true) {
return applyWithDimming.call(
this,
consoleStore,
originalMethod,
methodName,
args
)
} else {
return originalMethod.apply(this, args)
}
🤖 Prompt for AI Agents
In
`@packages/next/src/server/node-environment-extensions/console-dim.external.tsx`
around lines 189 - 215, The code calls cacheSignal() unguarded which will throw
for React builds that don't implement it; update the signal assignment to call
cacheSignal via optional chaining on the method call (e.g.,
getClientReact()?.cacheSignal?.() ?? getServerReact()?.cacheSignal?.()) so that
when getClientReact() or getServerReact() lack cacheSignal the code falls back
to the existing workUnitStore logic; ensure this change preserves the subsequent
use of signal and does not alter the applyWithDimming/originalMethod branches
(symbols: getClientReact, getServerReact, cacheSignal, signal, applyWithDimming,
originalMethod, methodName, args, consoleStore).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants