nextjs - ✅(Solved) Fix Fast setImmediate should not change Node behavior for everybody else! [1 pull requests, 11 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#88466Fetched 2026-04-08 02:04:48
View on GitHub
Comments
11
Participants
3
Timeline
29
Reactions
1
Author
Timeline (top)
commented ×11subscribed ×7mentioned ×4labeled ×2

Root Cause

By doing you are changing the behavior for every other libraries out there and some might rely on the current behavior. They probably shouldn't (again because it is non deterministic) but that's the status.

Fix Action

Fix / Workaround

Then I don't think patching Node is the way to go here.

The expected behavior is that nodeTimerPromises.setImmediate is not patched.

PR fix notes

PR #86018: [Cache Components] Fast setImmediate

Description (problem / solution / changelog)

This place is not a place of honor... no highly esteemed deed is commemorated here... nothing valued is here. What is here was dangerous and repulsive to us. This message is a warning about danger.

you think you know when setImmediate is supposed to run? no you don't


This PR introduces a patch to the Node setImmediate builtin. The patch is enabled by calling DANGEROUSLY_runPendingImmediatesAfterCurrentTask(). All immediates scheduled after that point (until the end of the task) will be captured and executed right after that task (after process.nextTick and microtasks). This applies to immediates scheduled from immediates as well.

This is relevant when scheduling back-to-back timeouts for staged rendering in Cache Components:

setTimeout(() => {
  // runs first
  
  DANGEROUSLY_runPendingImmediatesAfterCurrentTask() // enable the patch
  setImmediate(() => {
    // runs second (normally, it'd run last!)
  })
})

setTimeout(() => {
  // runs third
})

the immediate scheduled from inside the first timeout will always run before the second timeout.

A side-effect of this is that setImmediate will no longer be considered IO in the Cache Components rendering model, because immediates will always run before we advance the stage (or abort a prerender), and thus can't result in a dynamic hole. This brings the runtime behavior in line with React Devtools, which does not show setImmediate as IO.

This patch also has some observable differences in behavior from native setImmediate, mostly to do with uncaught errors:

  1. sync errors in process.nextTick no longer interrupt processTicksAndRejections (which would make us move onto the next event loop step, and run the rest of the nextTick queue after the next task, breaking our scheduling). They're rethrown in a microtask, which changes the timing of uncaughtException a bit.
  2. unhandled rejections will trigger unhandledRejection after all fast immediates are done executing, not after each immediate. This happens because our userspace immediate scheduling relies on nextTick, and rejections are only processed after everything else in processTicksAndRejections is done.

We hope that these divergences in behavior are niche enough to not affect any real world code. Both are potentially fixable by managing our own nextTick queue, more, but we'll try to avoid that complexity for now.

Changed files

  • packages/next/errors.json (modified, +14/-1)
  • packages/next/src/server/app-render/app-render-prerender-utils.ts (modified, +26/-8)
  • packages/next/src/server/app-render/app-render-render-utils.ts (modified, +15/-2)
  • packages/next/src/server/app-render/app-render-scheduling.ts (modified, +6/-2)
  • packages/next/src/server/node-environment-extensions/fast-set-immediate.external.test.ts (added, +1445/-0)
  • packages/next/src/server/node-environment-extensions/fast-set-immediate.external.ts (added, +797/-0)
  • packages/next/src/server/node-environment.ts (modified, +1/-0)
  • test/development/app-dir/cache-components-tasks/cache-components-tasks.test.ts (added, +190/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/with-prefetch-config/app/layout.tsx (added, +7/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/with-prefetch-config/app/page.tsx (added, +14/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/with-prefetch-config/app/revalidate/route.ts (added, +8/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/with-prefetch-config/app/shared.tsx (added, +31/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/with-prefetch-config/app/simple/layout.tsx (added, +22/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/with-prefetch-config/app/simple/page.tsx (added, +16/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/with-prefetch-config/next.config.ts (added, +7/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/without-prefetch-config/app/layout.tsx (added, +7/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/without-prefetch-config/app/page.tsx (added, +14/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/without-prefetch-config/app/revalidate/route.ts (added, +8/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/without-prefetch-config/app/shared.tsx (added, +31/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/without-prefetch-config/app/simple/layout.tsx (added, +20/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/without-prefetch-config/app/simple/page.tsx (added, +16/-0)
  • test/development/app-dir/cache-components-tasks/fixtures/without-prefetch-config/next.config.ts (added, +7/-0)

Code Example

Any OS
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/vercel/next.js/blob/9a55a09627ba682b50e4e90fa9dc040bbb5bf00b/packages/next/src/server/node-environment-extensions/fast-set-immediate.external.ts#L45

To Reproduce

  • Implemented in this PR
  • cc @lubieowoce

The PR says

you think you know when setImmediate is supposed to run? no you don't

Well I don't exactly agree with this. If you read the language spec, you know... that it is not deterministic (and different runtimes could have a different behavior and still be compliant with the doc).

Then I don't think patching Node is the way to go here.

By doing you are changing the behavior for every other libraries out there and some might rely on the current behavior. They probably shouldn't (again because it is non deterministic) but that's the status.

A much better solution would be to create a new scheduler matching the behavior expected by Next and use that but not change the behavior of Node globally.

(Note: we initially discovered this because the Cloudflare workerd runtime has a bug preventing setImmediate from being overwritten - this is being fixed)

Current vs. Expected behavior

The expected behavior is that nodeTimerPromises.setImmediate is not patched.

Provide environment information

Any OS

Which area(s) are affected? (Select all that apply)

Runtime, Not sure

Which stage(s) are affected? (Select all that apply)

Other (Deployed)

Additional context

No response

extent analysis

TL;DR

Create a new scheduler that matches the expected behavior for Next.js without patching Node's setImmediate globally.

Guidance

  • Instead of modifying Node's setImmediate, design a custom scheduler that aligns with Next.js's requirements.
  • This approach prevents unintended behavior changes in other libraries that might rely on the current setImmediate behavior.
  • Consider the non-deterministic nature of setImmediate as per the language spec and how different runtimes might handle it differently.
  • Evaluate the impact of creating a new scheduler on the overall system, especially considering the fix for the Cloudflare workerd runtime bug.

Example

No specific code example is provided due to the lack of detailed implementation requirements in the issue.

Notes

The solution must carefully consider the implications of altering scheduling behavior, even if it's for a specific use case like Next.js, to avoid unforeseen consequences in a production environment.

Recommendation

Apply workaround: Create a custom scheduler for Next.js to avoid globally altering Node's setImmediate behavior, ensuring compatibility and predictability across different libraries and runtimes.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

nextjs - ✅(Solved) Fix Fast setImmediate should not change Node behavior for everybody else! [1 pull requests, 11 comments, 3 participants]