diff --git a/.changeset/hdr-revalidation-retry.md b/.changeset/hdr-revalidation-retry.md new file mode 100644 index 00000000000..949fb91bcf1 --- /dev/null +++ b/.changeset/hdr-revalidation-retry.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +retry HDR revalidations in development mode to aid in 3rd party server race conditions diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx index 821731a3dad..f56510d6e2e 100644 --- a/packages/remix-react/browser.tsx +++ b/packages/remix-react/browser.tsx @@ -30,6 +30,7 @@ declare global { }; var __remixRouteModules: RouteModules; var __remixManifest: EntryContext["manifest"]; + var __remixRevalidation: number | undefined; var $RefreshRuntime$: { performReactRefresh: () => void; }; @@ -139,6 +140,7 @@ if (import.meta && import.meta.hot) { }, 1); } }); + window.__remixRevalidation = (window.__remixRevalidation || 0) + 1; router.revalidate(); } ); diff --git a/packages/remix-react/data.ts b/packages/remix-react/data.ts index a065b65c407..675b8413768 100644 --- a/packages/remix-react/data.ts +++ b/packages/remix-react/data.ts @@ -14,7 +14,7 @@ export function isCatchResponse(response: Response): boolean { return response.headers.get("X-Remix-Catch") != null; } -export function isErrorResponse(response: Response): boolean { +export function isErrorResponse(response: any): response is Response { return response.headers.get("X-Remix-Error") != null; } @@ -28,7 +28,8 @@ export function isDeferredResponse(response: Response): boolean { export async function fetchData( request: Request, - routeId: string + routeId: string, + retry = 0 ): Promise { let url = new URL(request.url); url.searchParams.set("_data", routeId); @@ -47,7 +48,24 @@ export async function fetchData( : await request.formData(); } - let response = await fetch(url.href, init); + if (retry > 0) { + // Retry up to 3 times waiting 50, 250, 1250 ms + // between retries for a total of 1550 ms before giving up. + await new Promise((resolve) => setTimeout(resolve, 5 ** retry * 10)); + } + + let revalidation = window.__remixRevalidation; + let response = await fetch(url.href, init).catch((error) => { + if ( + typeof revalidation === "number" && + revalidation === window.__remixRevalidation && + error?.name === "TypeError" && + retry < 3 + ) { + return fetchData(request, routeId, retry + 1); + } + throw error; + }); if (isErrorResponse(response)) { let data = await response.json();