next.js
60ddf096 - Cancel a superseded Server Components HMR refresh's server-side work (#95486)

Commit
112 days ago
Cancel a superseded Server Components HMR refresh's server-side work (#95486) When rapid edits overlap Server Components HMR refreshes in `next dev`, only the newest refresh can commit. The client already aborts a superseded refresh's fetch, which closes its response. We use that response-close to stop the server work the superseded refresh started, so the dev server no longer runs a render (and, under Cache Components, a validation) whose result is discarded. When the `serverComponentsHmrCancellation` flag is enabled and the request is an HMR refresh over a Node response, we derive an abort signal from `signalFromNodeResponse(ctx.res.originalResponse)` and thread it into the Flight render. This covers both dev RSC render paths: the Cache Components staged render in `generateDynamicFlightRenderResultWithStagesInDev` and the non-Cache-Components render in `generateDynamicFlightRenderResult`. The render is aborted through `renderToNodeFlightStream`, which calls `abort()` on the pipeable that `renderToPipeableStream` returns when the signal fires. React's Node Flight API has no `signal` option, unlike the Web `renderToReadableStream`, aborting the returned pipeable is the intended way to stop the render. This also removes the incorrect `signal` field from the `renderToPipeableStream` type declaration and derives `FlightRenderOptions` from `renderToReadableStream`'s options, so the render wrappers are no longer typed as `any`. Under Cache Components the superseded refresh additionally skips its detached validation, which only runs on the staged render, so it never validates a discarded tree, and any error thrown while its background renders tear down is swallowed. The aborted Flight renders surface as abort errors that `createReactServerErrorHandler` already ignores, so nothing reaches the error overlay or the CLI. The `hmr-rsc-cancellation` suite exercises both render paths, with Cache Components controlled by the `__NEXT_CACHE_COMPONENTS` test shard rather than the fixture config. A child component logs when React renders it, and the test asserts the superseded refresh never logs, since its render is aborted before reaching the child, while the committed refresh does. The detached-validation assertions run only when Cache Components is enabled.
Author
Parents
Loading