-
Notifications
You must be signed in to change notification settings - Fork 50.2k
Open
Labels
Status: UnconfirmedA potential issue that we haven't yet confirmed as a bugA potential issue that we haven't yet confirmed as a bug
Description
Steps To Reproduce
- Create a React Server Component with a short async delay (~5ms) wrapped in Suspense
- Trigger a revalidation (e.g., via revalidatePath() in Next.js)
- The server sends correct updated data, but the client UI does not update
Link to code example: vercel/next.js#87529
The current behavior
After calling revalidatePath(), the browser receives the correct Flight data with the updated counter value, but the UI remains stuck showing the old value. This only happens:
- In production mode
- With short async delays (~5ms)
- When the component is wrapped in Suspense
next-issue.mov
The issue was introduced in commit that upgraded React from eaee5308-20250728 to 9be531cd-20250729.
Root cause
The new resolveLazy function in ReactChildFiber.js catches promises and throws SuspenseException:
export function resolveLazy<T>(lazyType: LazyComponentType<T, any>): T {
try {
if (__DEV__) {
return callLazyInitInDEV(lazyType);
}
const payload = lazyType._payload;
const init = lazyType._init;
return init(payload);
} catch (x) {
if (x !== null && typeof x === 'object' && typeof x.then === 'function') {
// This lazy Suspended. Treat this as if we called use() to unwrap it.
suspendedThenable = x;
if (__DEV__) {
needsToResetSuspendedThenableDEV = true;
}
throw SuspenseException;
}
throw x;
}
}When a lazy component's init() throws a short-lived promise (~5ms), this creates a race condition:
- resolveLazy catches the promise and stores it in suspendedThenable
- It throws SuspenseException to signal suspension
- The promise resolves before React has finished setting up its subscription
- React remains suspended, waiting for a signal that already passed
- The UI never updates
Previous working version
function resolveLazy(lazyType: any) {
if (__DEV__) {
return callLazyInitInDEV(lazyType);
}
const payload = lazyType._payload;
const init = lazyType._init;
return init(payload);
}Without the try-catch, promises propagate naturally through React's existing Suspense machinery, which handles them correctly.
The expected behavior
After revalidatePath() is called:
- The server sends updated Flight data
- React reconciles the new data
- The UI updates to show the new counter value
Nxmber and leeguooooogajanan0210 and leeguoooooApsiV11
Metadata
Metadata
Assignees
Labels
Status: UnconfirmedA potential issue that we haven't yet confirmed as a bugA potential issue that we haven't yet confirmed as a bug