Skip to content

Commit 5213a95

Browse files
authored
Remove deprecated ABORT_DELAY in favor of streamTimeout (#12478)
1 parent 0ea8e66 commit 5213a95

File tree

11 files changed

+60
-68
lines changed

11 files changed

+60
-68
lines changed

.changeset/wild-dogs-double.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Remove the leftover/unused `abortDelay` prop from `ServerRouter` and update the default `entry.server.tsx` to use the new `streamTimeout` value for Single Fetch
6+
7+
- The `abortDelay` functionality was removed in v7 as it was coupled to the `defer` implementation from Remix v2, but this removal of this prop was missed
8+
- If you were still using this prop in your `entry.server` file, it's likely your app is not aborting streams as you would expect and you will need to adopt the new [`streamTimeout`](https://reactrouter.com/explanation/special-files#streamtimeout) value introduced with Single Fetch

docs/explanation/special-files.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,32 @@ The `default` export of this module is a function that lets you create the respo
229229

230230
This module should render the markup for the current page using a `<ServerRouter>` element with the `context` and `url` for the current request. This markup will (optionally) be re-hydrated once JavaScript loads in the browser using the [client entry module][client-entry].
231231

232+
### `streamTimeout`
233+
234+
If you are [streaming] responses, you can export an optional `streamTimeout` value (in milliseconds) that will control the amount of time the server will wait for streamed promises to settle before rejecting outstanding promises them and closing the stream.
235+
236+
It's recommended to decouple this value from the timeout in which you abort the React renderer. You should always set the React rendering timeout to a higher value so it has time to stream down the underlying rejections from your `streamTimeout`.
237+
238+
```tsx lines=[1-2,13-15]
239+
// Reject all pending promises from handler functions after 10 seconds
240+
export const streamTimeout = 10000;
241+
242+
export default function handleRequest(...) {
243+
return new Promise((resolve, reject) => {
244+
// ...
245+
246+
const { pipe, abort } = renderToPipeableStream(
247+
<ServerRouter context={routerContext} url={request.url} />,
248+
{ /* ... */ }
249+
);
250+
251+
// Abort the streaming render pass after 11 seconds soto allow the rejected
252+
// boundaries to be flushed
253+
setTimeout(abort, streamTimeout + 1000);
254+
});
255+
}
256+
```
257+
232258
### `handleDataRequest`
233259

234260
You can export an optional `handleDataRequest` function that will allow you to modify the response of a data request. These are the requests that do not render HTML, but rather return the loader and action data to the browser once client-side hydration has occurred.
@@ -289,3 +315,4 @@ Note that this does not handle thrown `Response` instances from your `loader`/`a
289315
[rendertopipeablestream]: https://react.dev/reference/react-dom/server/renderToPipeableStream
290316
[rendertoreadablestream]: https://react.dev/reference/react-dom/server/renderToReadableStream
291317
[node-streaming-entry-server]: https://github.com/remix-run/react-router/blob/dev/packages/react-router-dev/config/defaults/entry.server.node.tsx
318+
[streaming]: ../how-to/suspense

integration/error-sanitization-test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,6 @@ test.describe("Error Sanitization", () => {
497497
import { ServerRouter, isRouteErrorResponse } from "react-router";
498498
import { renderToPipeableStream } from "react-dom/server";
499499
500-
const ABORT_DELAY = 5_000;
501-
502500
export default function handleRequest(
503501
request,
504502
responseStatusCode,
@@ -508,11 +506,7 @@ test.describe("Error Sanitization", () => {
508506
return new Promise((resolve, reject) => {
509507
let shellRendered = false;
510508
const { pipe, abort } = renderToPipeableStream(
511-
<ServerRouter
512-
context={remixContext}
513-
url={request.url}
514-
abortDelay={ABORT_DELAY}
515-
/>,
509+
<ServerRouter context={remixContext} url={request.url} />,
516510
{
517511
onShellReady() {
518512
shellRendered = true;
@@ -545,7 +539,7 @@ test.describe("Error Sanitization", () => {
545539
}
546540
);
547541
548-
setTimeout(abort, ABORT_DELAY);
542+
setTimeout(abort, 5000);
549543
});
550544
}
551545

integration/vite-dev-custom-entry-test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ const files: Files = async ({ port }) => ({
1414
import { ServerRouter } from "react-router";
1515
import { renderToPipeableStream } from "react-dom/server";
1616
17-
const ABORT_DELAY = 5_000;
18-
1917
export default function handleRequest(
2018
request: Request,
2119
responseStatusCode: number,
@@ -25,11 +23,7 @@ const files: Files = async ({ port }) => ({
2523
return new Promise((resolve, reject) => {
2624
let shellRendered = false;
2725
const { pipe, abort } = renderToPipeableStream(
28-
<ServerRouter
29-
context={remixContext}
30-
url={request.url}
31-
abortDelay={ABORT_DELAY}
32-
/>,
26+
<ServerRouter context={remixContext} url={request.url} />,
3327
{
3428
onShellReady() {
3529
shellRendered = true;
@@ -65,7 +59,7 @@ const files: Files = async ({ port }) => ({
6559
}
6660
);
6761
68-
setTimeout(abort, ABORT_DELAY);
62+
setTimeout(abort, 5000);
6963
});
7064
}
7165
`,

integration/vite-spa-mode-test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,6 @@ test.describe("SPA Mode", () => {
296296
import { ServerRouter } from "react-router";
297297
import { renderToPipeableStream } from "react-dom/server";
298298
299-
const ABORT_DELAY = 5_000;
300-
301299
export default function handleRequest(
302300
request: Request,
303301
responseStatusCode: number,
@@ -322,11 +320,7 @@ test.describe("SPA Mode", () => {
322320
const html = await new Promise((resolve, reject) => {
323321
let shellRendered = false;
324322
const { pipe, abort } = renderToPipeableStream(
325-
<ServerRouter
326-
context={remixContext}
327-
url={request.url}
328-
abortDelay={ABORT_DELAY}
329-
/>,
323+
<ServerRouter context={remixContext} url={request.url} />,
330324
{
331325
onAllReady() {
332326
shellRendered = true;
@@ -359,7 +353,7 @@ test.describe("SPA Mode", () => {
359353
}
360354
);
361355
362-
setTimeout(abort, ABORT_DELAY);
356+
setTimeout(abort, 5000);
363357
});
364358
365359
const shellHtml = fs

packages/react-router-dev/config/defaults/entry.server.node.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { isbot } from "isbot";
77
import type { RenderToPipeableStreamOptions } from "react-dom/server";
88
import { renderToPipeableStream } from "react-dom/server";
99

10-
const ABORT_DELAY = 5_000;
10+
export const streamTimeout = 5_000;
1111

1212
export default function handleRequest(
1313
request: Request,
@@ -28,11 +28,7 @@ export default function handleRequest(
2828
: "onShellReady";
2929

3030
const { pipe, abort } = renderToPipeableStream(
31-
<ServerRouter
32-
context={routerContext}
33-
url={request.url}
34-
abortDelay={ABORT_DELAY}
35-
/>,
31+
<ServerRouter context={routerContext} url={request.url} />,
3632
{
3733
[readyOption]() {
3834
shellRendered = true;
@@ -65,6 +61,8 @@ export default function handleRequest(
6561
}
6662
);
6763

68-
setTimeout(abort, ABORT_DELAY);
64+
// Abort the rendering stream after the `streamTimeout` so it has tine to
65+
// flush down the rejected boundaries
66+
setTimeout(abort, streamTimeout + 1000);
6967
});
7068
}

packages/react-router/lib/dom/ssr/entry.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export interface FrameworkContextObject {
1717
serverHandoffString?: string;
1818
future: FutureConfig;
1919
isSpaMode: boolean;
20-
abortDelay?: number;
2120
serializeError?(error: Error): SerializedError;
2221
renderMeta?: {
2322
didRenderScripts?: boolean;

packages/react-router/lib/dom/ssr/server.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { StreamTransfer } from "./single-fetch";
1111
export interface ServerRouterProps {
1212
context: EntryContext;
1313
url: string | URL;
14-
abortDelay?: number;
1514
nonce?: string;
1615
}
1716

@@ -25,7 +24,6 @@ export interface ServerRouterProps {
2524
export function ServerRouter({
2625
context,
2726
url,
28-
abortDelay,
2927
nonce,
3028
}: ServerRouterProps): ReactElement {
3129
if (typeof url === "string") {
@@ -79,7 +77,6 @@ export function ServerRouter({
7977
future: context.future,
8078
isSpaMode: context.isSpaMode,
8179
serializeError: context.serializeError,
82-
abortDelay,
8380
renderMeta: context.renderMeta,
8481
}}
8582
>

packages/react-router/lib/server-runtime/single-fetch.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,12 @@ export function encodeViaTurboStream(
313313
) {
314314
let controller = new AbortController();
315315
// How long are we willing to wait for all of the promises in `data` to resolve
316-
// before timing out? We default this to 50ms shorter than the default value for
317-
// `ABORT_DELAY` in our built-in `entry.server.tsx` so that once we reject we
318-
// have time to flush the rejections down through React's rendering stream before `
319-
// we call abort() on that. If the user provides their own it's up to them to
320-
// decouple the aborting of the stream from the aborting of React's renderToPipeableStream
316+
// before timing out? We default this to 50ms shorter than the default value
317+
// of 5000ms we had in `ABORT_DELAY` in Remix v2 that folks may still be using
318+
// in RR v7 so that once we reject we have time to flush the rejections down
319+
// through React's rendering stream before we call `abort()` on that. If the
320+
// user provides their own it's up to them to decouple the aborting of the
321+
// stream from the aborting of React's `renderToPipeableStream`
321322
let timeoutId = setTimeout(
322323
() => controller.abort(new Error("Server Timeout")),
323324
typeof streamTimeout === "number" ? streamTimeout : 4950

playground/framework-express/app/entry.server.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import { ServerRouter } from "react-router";
66
import { isbot } from "isbot";
77
import { renderToPipeableStream } from "react-dom/server";
88

9-
const ABORT_DELAY = 5_000;
10-
119
export default function handleRequest(
1210
request: Request,
1311
responseStatusCode: number,
@@ -39,11 +37,7 @@ function handleBotRequest(
3937
return new Promise((resolve, reject) => {
4038
let shellRendered = false;
4139
const { pipe, abort } = renderToPipeableStream(
42-
<ServerRouter
43-
context={reactRouterContext}
44-
url={request.url}
45-
abortDelay={ABORT_DELAY}
46-
/>,
40+
<ServerRouter context={reactRouterContext} url={request.url} />,
4741
{
4842
onAllReady() {
4943
shellRendered = true;
@@ -76,7 +70,7 @@ function handleBotRequest(
7670
}
7771
);
7872

79-
setTimeout(abort, ABORT_DELAY);
73+
setTimeout(abort, 5000);
8074
});
8175
}
8276

@@ -89,11 +83,7 @@ function handleBrowserRequest(
8983
return new Promise((resolve, reject) => {
9084
let shellRendered = false;
9185
const { pipe, abort } = renderToPipeableStream(
92-
<ServerRouter
93-
context={reactRouterContext}
94-
url={request.url}
95-
abortDelay={ABORT_DELAY}
96-
/>,
86+
<ServerRouter context={reactRouterContext} url={request.url} />,
9787
{
9888
onShellReady() {
9989
shellRendered = true;
@@ -126,6 +116,6 @@ function handleBrowserRequest(
126116
}
127117
);
128118

129-
setTimeout(abort, ABORT_DELAY);
119+
setTimeout(abort, 5000);
130120
});
131121
}

0 commit comments

Comments
 (0)