From 5012b94c13f79bd567faeb3732f10cf20fa3332e Mon Sep 17 00:00:00 2001 From: Jacob Ebey Date: Wed, 3 Dec 2025 17:18:48 -0800 Subject: [PATCH] enhance thrown redirects on the client --- packages/react-router/lib/hooks.tsx | 20 ++++++++-------- playground/rsc-vite/src/routes.ts | 5 ++++ .../rsc-vite/src/routes/render-redirects.tsx | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 playground/rsc-vite/src/routes/render-redirects.tsx diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index 1207aa9e00..60708f416f 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -1090,7 +1090,7 @@ export class RenderErrorBoundary extends React.Component< } } -const errorRedirectHandledMap = new WeakMap(); +const errorRedirectHandledMap = new WeakMap>(); function RSCErrorHandler({ children, error, @@ -1099,7 +1099,6 @@ function RSCErrorHandler({ error: unknown; }) { let { basename } = React.useContext(NavigationContext); - let navigate = useNavigate(); if ( typeof error === "object" && @@ -1109,22 +1108,25 @@ function RSCErrorHandler({ ) { let redirect = decodeRedirectErrorDigest(error.digest); if (redirect) { + let existingRedirect = errorRedirectHandledMap.get(error); + if (existingRedirect) throw existingRedirect; + let parsed = parseToInfo(redirect.location, basename); if (isBrowser && !errorRedirectHandledMap.get(error)) { - errorRedirectHandledMap.set(error, true); - if (parsed.isExternal || redirect.reloadDocument) { window.location.href = parsed.absoluteURL || parsed.to; } else { - // @ts-expect-error - Needs React 19 types - React.startTransition(() => { - return navigate(parsed.to, { + const redirectPromise: Promise = Promise.resolve().then(() => + window.__reactRouterDataRouter!.navigate(parsed.to, { replace: redirect.replace, - }); - }); + }), + ); + errorRedirectHandledMap.set(error, redirectPromise); + throw redirectPromise; } } + return ( import("./routes/about/about"), }, + { + id: "render-redirect", + path: "render-redirect/:id?", + lazy: () => import("./routes/render-redirects"), + }, { id: "parent", path: "parent", diff --git a/playground/rsc-vite/src/routes/render-redirects.tsx b/playground/rsc-vite/src/routes/render-redirects.tsx new file mode 100644 index 0000000000..ceb4f757aa --- /dev/null +++ b/playground/rsc-vite/src/routes/render-redirects.tsx @@ -0,0 +1,23 @@ +import { Link, redirect } from "react-router"; + +export default function RenderRedirect({ + params: { id }, +}: { + params: { id?: string }; +}) { + if (id === "redirect") { + throw redirect("/render-redirect/redirected"); + } + + if (id === "external") { + throw redirect("https://example.com/"); + } + + return ( + <> +

{id || "home"}

+ Redirect + External + + ); +}