nextjs - ✅(Solved) Fix Cache Components causes fetch to hang [1 pull requests, 8 comments, 4 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#86662Fetched 2026-04-08 02:09:56
View on GitHub
Comments
8
Participants
4
Timeline
31
Reactions
5
Author
Timeline (top)
subscribed ×9commented ×8mentioned ×7closed ×1

Fix Action

Fixed

PR fix notes

PR #86690: [CC] Fix hanging dynamic promise when abandoning render

Description (problem / solution / changelog)

Fixes #86662

The bug was introduced in #85747, where we added RenderStage.Abandoned. We forgot to update the logic that rejects promises on abort, so if a render was abandoned (i.e. restarted due to a cache miss), then promises waiting for the dynamic stage would hang forever.

This is not generally observable unless the promise is persisted/cached in userspace. Userspace caches are problematic in Cache Components and we discourage their usage, but the test app included here should still work as expected (i.e. without hanging forever while rendering).

Changed files

  • packages/next/src/server/app-render/staged-rendering.ts (modified, +4/-1)
  • test/development/app-dir/cache-components-reused-promise/app/layout.tsx (added, +7/-0)
  • test/development/app-dir/cache-components-reused-promise/app/page.tsx (added, +71/-0)
  • test/development/app-dir/cache-components-reused-promise/cache-components-reused-promise.test.ts (added, +12/-0)
  • test/development/app-dir/cache-components-reused-promise/next.config.ts (added, +7/-0)

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Fri Nov 15 15:12:52 PST 2024; root:xnu-10063.141.1.702.7~1/RELEASE_ARM64_T6031
  Available memory (MB): 131072
  Available CPU cores: 16
Binaries:
  Node: 22.20.0
  npm: 10.9.3
  Yarn: 1.22.22
  pnpm: 10.18.1
Relevant Packages:
  next: 16.1.0-canary.4 // Latest available version is detected (16.1.0-canary.4).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

---

async function cachedFn(): Promise<void> {
  "use cache";
  await new Promise((resolve) => setTimeout(resolve, 100));
}

export default async function Page() {
  await cachedFn();
  return <p>page content</p>;
}

---

const pending = new Map<string, Promise<any>>();

async function dedup(key: string): Promise<any> {
  let existingPromise;
  while ((existingPromise = pending.get(key))) {
    console.log("await existing");
    return await existingPromise;
  }

  console.log("starting");
  const promise = fetch("https://httpbin.org/get")
    .then((res) => {
      return res.json();
    })
    .finally(() => {
      console.log("done");
      pending.delete(key);
    });

  pending.set(key, promise);
  return promise;
}

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  await dedup("test");
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

---

starting
await existing
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/gaearon/next-bug-repro

To Reproduce

  1. Start https://github.com/gaearon/next-bug-repro
  2. Open http://localhost:3000/

Current vs. Expected behavior

Current behavior: it hangs

Expected behavior: it doesn't hang

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Fri Nov 15 15:12:52 PST 2024; root:xnu-10063.141.1.702.7~1/RELEASE_ARM64_T6031
  Available memory (MB): 131072
  Available CPU cores: 16
Binaries:
  Node: 22.20.0
  npm: 10.9.3
  Yarn: 1.22.22
  pnpm: 10.18.1
Relevant Packages:
  next: 16.1.0-canary.4 // Latest available version is detected (16.1.0-canary.4).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

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

cacheComponents

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

next dev (local)

Additional context

This is in the repo already, but I'll briefly state the problem.

First, enable Cache Components.

Then, suppose we have page.ts like this:

async function cachedFn(): Promise<void> {
  "use cache";
  await new Promise((resolve) => setTimeout(resolve, 100));
}

export default async function Page() {
  await cachedFn();
  return <p>page content</p>;
}

And layout.ts like this:

const pending = new Map<string, Promise<any>>();

async function dedup(key: string): Promise<any> {
  let existingPromise;
  while ((existingPromise = pending.get(key))) {
    console.log("await existing");
    return await existingPromise;
  }

  console.log("starting");
  const promise = fetch("https://httpbin.org/get")
    .then((res) => {
      return res.json();
    })
    .finally(() => {
      console.log("done");
      pending.delete(key);
    });

  pending.set(key, promise);
  return promise;
}

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  await dedup("test");
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

The Map for pending Promises is something I extracted from a third-party OAuth client I'm using so I don't really have control over it. It seems designed to ensure serial execution. I suspect this is a reasonable pattern that might show up in different places so I wouldn't expect Next to break on it. If I'm not mistaken, the original piece of code (from which Claude extracted this minimal repro) was here (cc @matthieusieben in case he can provide more context on the requirement of OAuth flow).

The logs I see on start:

starting
await existing

So basically:

  1. The initial fetch runs but never completes (why?)
  2. The pending Promise stays in the map
  3. Any future attempt (maybe the first one was a prerender?) waits for that Promise forever

The most frustrating/surprising thing is that removing 'use cache' from the layout.ts (which intuitively feels completely unrelated to this code!) "fixes" the issue in page.ts.

My questions are:

  1. How to make it work with the original code (surely we can't expect the ecosystem to remove these types of locks from some flows)
  2. Is it a bug that mere presence of "use cache" on a completely different function causes this to break?

extent analysis

TL;DR

The issue can be resolved by re-examining the usage of "use cache" directive and its impact on the execution of asynchronous functions, potentially leading to a deadlock scenario.

Guidance

  1. Review the cache usage: Investigate how the "use cache" directive affects the execution of cachedFn and its interaction with the dedup function in layout.ts, as it seems to cause a deadlock.
  2. Analyze the promise chain: Examine the promise chain in dedup to ensure it is properly resolved and removed from the pending map, avoiding infinite waits.
  3. Check for circular dependencies: Verify if there are any circular dependencies between components or functions that could lead to a deadlock when caching is enabled.
  4. Test without caching: Temporarily remove caching to isolate the issue and understand the behavior without it, as the problem seems to be caching-related.

Example

No specific code example can be provided without further investigation, but reviewing the dedup function and its interaction with caching might look like this:

async function dedup(key: string): Promise<any> {
  //...
  const promise = fetch("https://httpbin.org/get")
   .then((res) => {
      return res.json();
    })
   .finally(() => {
      console.log("done");
      pending.delete(key);
    });
  // Ensure promise resolution and removal from pending map
  promise.catch((error) => {
    console.error("Error in dedup:", error);
    pending.delete(key);
  });
  //...
}

Notes

The provided information suggests a complex interaction between caching, promise resolution, and component execution. Further investigation is needed to fully understand and resolve the issue.

Recommendation

Apply a workaround by re-examining and potentially modifying the caching strategy to avoid deadlocks, as the current implementation seems to cause issues with asynchronous function execution.

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