diff --git a/.changeset/strong-penguins-retire.md b/.changeset/strong-penguins-retire.md new file mode 100644 index 0000000000..020d8f7472 --- /dev/null +++ b/.changeset/strong-penguins-retire.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Allow redirects to be returned from client side middleware diff --git a/packages/react-router/__tests__/router/context-middleware-test.tsx b/packages/react-router/__tests__/router/context-middleware-test.tsx index 8475937dc1..b0031e8fea 100644 --- a/packages/react-router/__tests__/router/context-middleware-test.tsx +++ b/packages/react-router/__tests__/router/context-middleware-test.tsx @@ -1684,6 +1684,110 @@ describe("context/middleware", () => { }); }); }); + + describe("redirects", () => { + it("allows you to return redirects before next from client middleware", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + path: "/redirect", + middleware: [ + async () => { + return redirect("/target"); + }, + ], + }, + { + path: "/target", + }, + ], + }); + + await router.navigate("/redirect"); + expect(router.state.location.pathname).toBe("/target"); + }); + + it("allows you to return redirects after next from client middleware", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + path: "/redirect", + middleware: [ + async (_, next) => { + await next(); + return redirect("/target"); + }, + ], + }, + { + path: "/target", + }, + ], + }); + + await router.navigate("/redirect"); + expect(router.state.location.pathname).toBe("/target"); + }); + + it("allows you to throw redirects before next from client middleware", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + path: "/redirect", + middleware: [ + async () => { + throw redirect("/target"); + }, + ], + }, + { + path: "/target", + }, + ], + }); + + await router.navigate("/redirect"); + expect(router.state.location.pathname).toBe("/target"); + }); + + it("allows you to throw redirects after next from client middleware", async () => { + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + }, + { + path: "/redirect", + middleware: [ + async (_, next) => { + await next(); + throw redirect("/target"); + }, + ], + }, + { + path: "/target", + }, + ], + }); + + await router.navigate("/redirect"); + expect(router.state.location.pathname).toBe("/target"); + }); + }); }); describe("middleware - handler.query", () => { diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index da9d2557fe..4c6b4a7f97 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -5664,7 +5664,13 @@ function runClientMiddlewarePipeline( return runMiddlewarePipeline( args, handler, - (r) => r, // No post-processing needed on the client + (r) => { + // Throw any redirect responses to short circuit + if (isRedirectResponse(r)) { + throw r; + } + return r; + }, isDataStrategyResults, errorHandler, );