From eb72a6f009c9994c03c4686325e171569cd669df Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 23 Sep 2025 14:29:58 -0400 Subject: [PATCH 1/2] Add back-compat for manifest parameter --- .changeset/rare-jobs-remember.md | 5 ++++ .../react-router/lib/server-runtime/server.ts | 26 +++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 .changeset/rare-jobs-remember.md diff --git a/.changeset/rare-jobs-remember.md b/.changeset/rare-jobs-remember.md new file mode 100644 index 0000000000..6ab9615576 --- /dev/null +++ b/.changeset/rare-jobs-remember.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +[REMOVE] Follow up to https://github.com/remix-run/react-router/pull/14321 to avoid issues with rolling deployments diff --git a/packages/react-router/lib/server-runtime/server.ts b/packages/react-router/lib/server-runtime/server.ts index d3b78ae510..ad5ac91734 100644 --- a/packages/react-router/lib/server-runtime/server.ts +++ b/packages/react-router/lib/server-runtime/server.ts @@ -354,19 +354,15 @@ async function handleManifestRequest( let patches: Record = {}; - if (url.searchParams.has("paths")) { + // Support both the old (`p`) and new formats (`paths`) to avoid issues during + // rolling deployments where an old client hits a new server + if (url.searchParams.has("p") || url.searchParams.has("paths")) { let paths = new Set(); - // In addition to responding with the patches for the requested paths, we - // need to include patches for each partial path so that we pick up any - // pathless/index routes below ancestor segments. So if we - // get a request for `/parent/child`, we need to look for a match on `/parent` - // so that if a `parent._index` route exists we return it so it's available - // for client side matching if the user routes back up to `/parent`. - // This is the same thing we do on initial load in via - // `getPartialManifest()` - let pathParam = url.searchParams.get("paths") || ""; - let requestedPaths = pathParam.split(",").filter(Boolean); + let requestedPaths = url.searchParams.has("paths") + ? (url.searchParams.get("paths") || "").split(",").filter(Boolean) + : url.searchParams.getAll("p"); + requestedPaths.forEach((path) => { if (!path.startsWith("/")) { path = `/${path}`; @@ -378,6 +374,14 @@ async function handleManifestRequest( }); }); + // In addition to responding with the patches for the requested paths, we + // need to include patches for each partial path so that we pick up any + // pathless/index routes below ancestor segments. So if we + // get a request for `/parent/child`, we need to look for a match on `/parent` + // so that if a `parent._index` route exists we return it so it's available + // for client side matching if the user routes back up to `/parent`. + // This is the same thing we do on initial load in via + // `getPartialManifest()` for (let path of paths) { let matches = matchServerRoutes(routes, path, build.basename); if (matches) { From 5cad5f8e66db8094d991f74510c74872af31832a Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 23 Sep 2025 14:56:25 -0400 Subject: [PATCH 2/2] Handle new client/old server --- .../react-router/lib/dom/ssr/fog-of-war.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/react-router/lib/dom/ssr/fog-of-war.ts b/packages/react-router/lib/dom/ssr/fog-of-war.ts index 7541bd5383..c7c36eab7c 100644 --- a/packages/react-router/lib/dom/ssr/fog-of-war.ts +++ b/packages/react-router/lib/dom/ssr/fog-of-war.ts @@ -242,6 +242,25 @@ export async function fetchAndApplyManifestPatches( try { let res = await fetch(url, { signal }); + // To avoid issues during rolling deployments if we get back a 400 when + // using `paths`, it's likely we hit an old server so retry the request + // with the old `p` parameters + if (res.status === 400) { + let body = await res.text(); + if (body !== "Invalid Request") { + throw new Error(`${res.status} ${res.statusText}`); + } + const retrySearchParams = new URLSearchParams(); + paths.sort().forEach((path) => retrySearchParams.append("p", path)); + retrySearchParams.set("version", manifest.version); + let url = new URL( + getManifestPath(manifestPath, basename), + window.location.origin, + ); + url.search = retrySearchParams.toString(); + res = await fetch(url, { signal }); + } + if (!res.ok) { throw new Error(`${res.status} ${res.statusText}`); } else if (