Issue with Server Actions + useTransition hanging indefinitely
Answered
Sad man posted this in #help-forum
Sad manOP
Hi everyone,
I’m running into a specific issue where my UI gets stuck in a pending state after a Server Action completes. The backend updates successfully, but the UI does not update until I manually reload the page. This issue is intermittent it works correctly about 7 out of 10 times, but the other 30% of the time the useTransition hook never resolves, leaving the spinner spinning indefinitely.
The Context:
Next.js Version: ^16.0.10
ENV=build/production (In dev it is working fine)
Architecture: A Client Component (CartQuantity) triggers a Server Action (updateCartItem).
Key Config: I am using cacheComponents: true in next.config.ts.
The Problem:
I click the generic +/- button.
startTransition triggers the Server Action.
Backend succeeds: My API gateway logs show the PATCH was successful and the subsequent GET requests (triggered by revalidation) return 200 OK with the new data.
Frontend hangs: The pending state from useTransition remains true. The UI never updates with the new quantity.
This issue seems specific to the aggressive production caching, as it work perfectly in development environments but fails reliably in production builds.
What I have tried (that did NOT fix it):
revalidatePath("/cart"): Ensures the cache is purged on the server, but the client doesn't pick it up.
router.refresh(): Calling this manually in the client component after the action.
redirect(): Trying to force a navigation.
refresh() (next/cache): Attempted to force refresh from the action side.
Nothing seems to break the pending state or force the UI to render the new server value when cacheComponents is enabled.
Has anyone experienced useTransition hanging specifically when component caching is enabled? Is there a specific way to bust the client-side router cache for this setup?
I have been scratching my head around this since yesterday morning. If anyone can help me with this, that would be incredibly helpful.
I’m running into a specific issue where my UI gets stuck in a pending state after a Server Action completes. The backend updates successfully, but the UI does not update until I manually reload the page. This issue is intermittent it works correctly about 7 out of 10 times, but the other 30% of the time the useTransition hook never resolves, leaving the spinner spinning indefinitely.
The Context:
Next.js Version: ^16.0.10
ENV=build/production (In dev it is working fine)
Architecture: A Client Component (CartQuantity) triggers a Server Action (updateCartItem).
Key Config: I am using cacheComponents: true in next.config.ts.
The Problem:
I click the generic +/- button.
startTransition triggers the Server Action.
Backend succeeds: My API gateway logs show the PATCH was successful and the subsequent GET requests (triggered by revalidation) return 200 OK with the new data.
Frontend hangs: The pending state from useTransition remains true. The UI never updates with the new quantity.
This issue seems specific to the aggressive production caching, as it work perfectly in development environments but fails reliably in production builds.
What I have tried (that did NOT fix it):
revalidatePath("/cart"): Ensures the cache is purged on the server, but the client doesn't pick it up.
router.refresh(): Calling this manually in the client component after the action.
redirect(): Trying to force a navigation.
refresh() (next/cache): Attempted to force refresh from the action side.
Nothing seems to break the pending state or force the UI to render the new server value when cacheComponents is enabled.
Has anyone experienced useTransition hanging specifically when component caching is enabled? Is there a specific way to bust the client-side router cache for this setup?
I have been scratching my head around this since yesterday morning. If anyone can help me with this, that would be incredibly helpful.
Answered by Sad man
For anyone else pulling their hair out over this, it turns out this is not an issue with your code,revalidatePath, or the Next.js Cache configuration.
The Root Cause: This is a binary-level race condition in the React 19 Fiber Reconciler (specifically regarding resolveLazy and useId replay tracking). When a Server Action resolves very quickly, the reconciler marks the boundary as suspended but fails to "replay" the update to the DOM, leaving the UI stuck in a pending state even though the data has arrived on the client.
The issue was introduced in Next.js commit that upgraded React from eaee5308-20250728 to 9be531cd-20250729. Which was fixed in a [React Patch](https://github.com/facebook/react/pull/35518) on Jan 17, 2026.
https://github.com/vercel/next.js/issues/86055
https://github.com/facebook/react/issues/35399
This is tracked in commit that created the issue in NextJs [e5c1dff8262b4d7dcef5bda0af9d9171196457bd](https://github.com/vercel/next.js/commit/e5c1dff8262b4d7dcef5bda0af9d9171196457bd)
The Fixes:
I tested multiple approaches. You have two options depending on your risk tolerance:
Option 1: If you must use Next.js 16, you cannot stay on the stable release (as of this comment) because it bundles the affected React binary. You must force-install the specific React Canary build that contains the fix:
Bash
Note: Ensure the installed react-dom version hash is from Jan 19, 2026 or later.
Option 2: What I did Since next@canary can be unstable for production, the safest path is to remove the buggy React 19 engine entirely. I downgraded to Next.js 15.3.6 and react 19.0.0
Result: The race condition does not exist in React 19.0.0 and React 18 The spinner works perfectly every time.
The Root Cause: This is a binary-level race condition in the React 19 Fiber Reconciler (specifically regarding resolveLazy and useId replay tracking). When a Server Action resolves very quickly, the reconciler marks the boundary as suspended but fails to "replay" the update to the DOM, leaving the UI stuck in a pending state even though the data has arrived on the client.
The issue was introduced in Next.js commit that upgraded React from eaee5308-20250728 to 9be531cd-20250729. Which was fixed in a [React Patch](https://github.com/facebook/react/pull/35518) on Jan 17, 2026.
https://github.com/vercel/next.js/issues/86055
https://github.com/facebook/react/issues/35399
This is tracked in commit that created the issue in NextJs [e5c1dff8262b4d7dcef5bda0af9d9171196457bd](https://github.com/vercel/next.js/commit/e5c1dff8262b4d7dcef5bda0af9d9171196457bd)
The Fixes:
I tested multiple approaches. You have two options depending on your risk tolerance:
Option 1: If you must use Next.js 16, you cannot stay on the stable release (as of this comment) because it bundles the affected React binary. You must force-install the specific React Canary build that contains the fix:
Bash
npm install next@canary react@canary react-dom@canary --forceNote: Ensure the installed react-dom version hash is from Jan 19, 2026 or later.
Option 2: What I did Since next@canary can be unstable for production, the safest path is to remove the buggy React 19 engine entirely. I downgraded to Next.js 15.3.6 and react 19.0.0
Result: The race condition does not exist in React 19.0.0 and React 18 The spinner works perfectly every time.
7 Replies
Sad manOP
Sad manOP
@B33fb0n3 I am sorry to ping you like this, I saw your responding on other post. Can you help me too?
@Sad man Hi everyone,
I’m running into a specific issue where my UI gets stuck in a pending state after a Server Action completes. The backend updates successfully, but the UI does not update until I manually reload the page. This issue is intermittent it works correctly about 7 out of 10 times, but the other 30% of the time the useTransition hook never resolves, leaving the spinner spinning indefinitely.
The Context:
Next.js Version: ^16.0.10
ENV=build/production (In dev it is working fine)
Architecture: A Client Component (CartQuantity) triggers a Server Action (updateCartItem).
Key Config: I am using cacheComponents: true in next.config.ts.
The Problem:
I click the generic +/- button.
startTransition triggers the Server Action.
Backend succeeds: My API gateway logs show the PATCH was successful and the subsequent GET requests (triggered by revalidation) return 200 OK with the new data.
Frontend hangs: The pending state from useTransition remains true. The UI never updates with the new quantity.
This issue seems specific to the aggressive production caching, as it work perfectly in development environments but fails reliably in production builds.
What I have tried (that did NOT fix it):
revalidatePath("/cart"): Ensures the cache is purged on the server, but the client doesn't pick it up.
router.refresh(): Calling this manually in the client component after the action.
redirect(): Trying to force a navigation.
refresh() (next/cache): Attempted to force refresh from the action side.
Nothing seems to break the pending state or force the UI to render the new server value when cacheComponents is enabled.
Has anyone experienced useTransition hanging specifically when component caching is enabled? Is there a specific way to bust the client-side router cache for this setup?
I have been scratching my head around this since yesterday morning. If anyone can help me with this, that would be incredibly helpful.
Have you read about this? This might be the problem.
https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition
https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition
Sad manOP
Yes this is not the issue If I stop remove useTranstion hook the issue still persist.
One more thing I want to add is.
Imagine I have multiple items in the cart. When I update one item, like increasing its quantity or removing it, the API call succeeds, but the UI gets stuck. The spinner keeps spinning and the UI does not update.
However, if I then interact with another item in the cart, like increasing its quantity or removing it, the UI suddenly updates and reflects the previous change as well. So the backend is working correctly, but the UI only re-renders when a second interaction happens.
That mean cache is being invalidating successfully but UI is stucking
Imagine I have multiple items in the cart. When I update one item, like increasing its quantity or removing it, the API call succeeds, but the UI gets stuck. The spinner keeps spinning and the UI does not update.
However, if I then interact with another item in the cart, like increasing its quantity or removing it, the UI suddenly updates and reflects the previous change as well. So the backend is working correctly, but the UI only re-renders when a second interaction happens.
That mean cache is being invalidating successfully but UI is stucking
Sad manOP
For anyone else pulling their hair out over this, it turns out this is not an issue with your code,revalidatePath, or the Next.js Cache configuration.
The Root Cause: This is a binary-level race condition in the React 19 Fiber Reconciler (specifically regarding resolveLazy and useId replay tracking). When a Server Action resolves very quickly, the reconciler marks the boundary as suspended but fails to "replay" the update to the DOM, leaving the UI stuck in a pending state even though the data has arrived on the client.
The issue was introduced in Next.js commit that upgraded React from eaee5308-20250728 to 9be531cd-20250729. Which was fixed in a [React Patch](https://github.com/facebook/react/pull/35518) on Jan 17, 2026.
https://github.com/vercel/next.js/issues/86055
https://github.com/facebook/react/issues/35399
This is tracked in commit that created the issue in NextJs [e5c1dff8262b4d7dcef5bda0af9d9171196457bd](https://github.com/vercel/next.js/commit/e5c1dff8262b4d7dcef5bda0af9d9171196457bd)
The Fixes:
I tested multiple approaches. You have two options depending on your risk tolerance:
Option 1: If you must use Next.js 16, you cannot stay on the stable release (as of this comment) because it bundles the affected React binary. You must force-install the specific React Canary build that contains the fix:
Bash
Note: Ensure the installed react-dom version hash is from Jan 19, 2026 or later.
Option 2: What I did Since next@canary can be unstable for production, the safest path is to remove the buggy React 19 engine entirely. I downgraded to Next.js 15.3.6 and react 19.0.0
Result: The race condition does not exist in React 19.0.0 and React 18 The spinner works perfectly every time.
The Root Cause: This is a binary-level race condition in the React 19 Fiber Reconciler (specifically regarding resolveLazy and useId replay tracking). When a Server Action resolves very quickly, the reconciler marks the boundary as suspended but fails to "replay" the update to the DOM, leaving the UI stuck in a pending state even though the data has arrived on the client.
The issue was introduced in Next.js commit that upgraded React from eaee5308-20250728 to 9be531cd-20250729. Which was fixed in a [React Patch](https://github.com/facebook/react/pull/35518) on Jan 17, 2026.
https://github.com/vercel/next.js/issues/86055
https://github.com/facebook/react/issues/35399
This is tracked in commit that created the issue in NextJs [e5c1dff8262b4d7dcef5bda0af9d9171196457bd](https://github.com/vercel/next.js/commit/e5c1dff8262b4d7dcef5bda0af9d9171196457bd)
The Fixes:
I tested multiple approaches. You have two options depending on your risk tolerance:
Option 1: If you must use Next.js 16, you cannot stay on the stable release (as of this comment) because it bundles the affected React binary. You must force-install the specific React Canary build that contains the fix:
Bash
npm install next@canary react@canary react-dom@canary --forceNote: Ensure the installed react-dom version hash is from Jan 19, 2026 or later.
Option 2: What I did Since next@canary can be unstable for production, the safest path is to remove the buggy React 19 engine entirely. I downgraded to Next.js 15.3.6 and react 19.0.0
Result: The race condition does not exist in React 19.0.0 and React 18 The spinner works perfectly every time.
Answer
Sad manOP
Security Note: If you downgrade, make sure to use next@15 (not an old specific patch) to ensure you are covered against [CVE-2025-66478](https://nextjs.org/blog/CVE-2025-66478).
TL;DR: It's a React 19 bug . Upgrade to the absolute latest Canary or downgrade to React 19.0.0 (Next.js 15 or Next.js 14) to solve it. Don't waste time debugging your revalidatePath logic.
TL;DR: It's a React 19 bug . Upgrade to the absolute latest Canary or downgrade to React 19.0.0 (Next.js 15 or Next.js 14) to solve it. Don't waste time debugging your revalidatePath logic.