From 8b692fbb6cab4978bc36c997cf043d2788a1d6ca Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Fri, 26 Sep 2025 11:42:20 -0400 Subject: [PATCH 1/3] Fix middleware on initial load without loaders --- .changeset/twelve-dogs-pump.md | 5 +++++ packages/react-router/lib/router/router.ts | 13 +++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 .changeset/twelve-dogs-pump.md diff --git a/.changeset/twelve-dogs-pump.md b/.changeset/twelve-dogs-pump.md new file mode 100644 index 0000000000..d61525d917 --- /dev/null +++ b/.changeset/twelve-dogs-pump.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Fix Data Mode regression causing a 404 during initial load in when `middleware` exists without any `loader` functions diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index 2c3e379345..38a5f909b5 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -5751,6 +5751,19 @@ function getDataStrategyMatch( !isMutationMethod(request.method) && (match.route.lazy || match.route.loader)) ) { + // If this match was marked `shouldLoad` due to a middleware and it + // doesn't have a `loader` to run and no `lazy` to add one, then we can + // just return undefined from the "loader" here + if ( + match.route.middleware && + match.route.middleware.length > 0 && + match.route.loader == null && + !match.route.lazy && + !handlerOverride + ) { + return Promise.resolve({ type: ResultType.data, result: undefined }); + } + return callLoaderOrAction({ request, match, From 41e180e2ab55246d980b3ecd864bac3eae09ab17 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Fri, 26 Sep 2025 13:33:26 -0400 Subject: [PATCH 2/3] Add test --- .../router/context-middleware-test.tsx | 56 +++++++++++++++++++ packages/react-router/lib/router/router.ts | 26 ++++----- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/packages/react-router/__tests__/router/context-middleware-test.tsx b/packages/react-router/__tests__/router/context-middleware-test.tsx index 9db190d373..cea82e235c 100644 --- a/packages/react-router/__tests__/router/context-middleware-test.tsx +++ b/packages/react-router/__tests__/router/context-middleware-test.tsx @@ -300,6 +300,62 @@ describe("context/middleware", () => { ]); }); + it("runs middleware on initialization even if no loaders exist", async () => { + let snapshot; + router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + middleware: [ + async ({ context }, next) => { + await next(); + // Grab a snapshot at the end of the upwards middleware chain + snapshot = context.get(orderContext); + }, + getOrderMiddleware(orderContext, "a"), + getOrderMiddleware(orderContext, "b"), + ], + children: [ + { + index: true, + middleware: [ + getOrderMiddleware(orderContext, "c"), + getOrderMiddleware(orderContext, "d"), + ], + }, + ], + }, + ], + }); + let initPromise = new Promise((r) => { + let unsub = router.subscribe((state) => { + if (state.initialized) { + unsub(); + r(undefined); + } + }); + }); + await router.initialize(); + await initPromise; + expect(router.state).toMatchObject({ + initialized: true, + location: { pathname: "/" }, + navigation: { state: "idle" }, + errors: null, + }); + expect(snapshot).toEqual([ + "a middleware - before next()", + "b middleware - before next()", + "c middleware - before next()", + "d middleware - before next()", + "d middleware - after next()", + "c middleware - after next()", + "b middleware - after next()", + "a middleware - after next()", + ]); + }); + it("runs middleware even if no loaders exist", async () => { let snapshot; router = createRouter({ diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index 38a5f909b5..1241d58770 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -5744,26 +5744,22 @@ function getDataStrategyMatch( return shouldRevalidateLoader(match, unstable_shouldRevalidateArgs); }, resolve(handlerOverride) { - if ( + let { lazy, loader, middleware } = match.route; + + let callHandler = isUsingNewApi || shouldLoad || (handlerOverride && !isMutationMethod(request.method) && - (match.route.lazy || match.route.loader)) - ) { - // If this match was marked `shouldLoad` due to a middleware and it - // doesn't have a `loader` to run and no `lazy` to add one, then we can - // just return undefined from the "loader" here - if ( - match.route.middleware && - match.route.middleware.length > 0 && - match.route.loader == null && - !match.route.lazy && - !handlerOverride - ) { - return Promise.resolve({ type: ResultType.data, result: undefined }); - } + (lazy || loader)); + + // If this match was marked `shouldLoad` due to a middleware and it + // doesn't have a `loader` to run and no `lazy` to add one, then we can + // just return undefined from the "loader" here + let isMiddlewareOnlyRoute = + middleware && middleware.length > 0 && loader == null && !lazy; + if (callHandler && !isMiddlewareOnlyRoute) { return callLoaderOrAction({ request, match, From 21239dcc5610c0844db24017ff8961c7d691b25b Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Fri, 26 Sep 2025 13:37:38 -0400 Subject: [PATCH 3/3] Update check --- packages/react-router/lib/router/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-router/lib/router/router.ts b/packages/react-router/lib/router/router.ts index 1241d58770..4cd9d1df51 100644 --- a/packages/react-router/lib/router/router.ts +++ b/packages/react-router/lib/router/router.ts @@ -5757,7 +5757,7 @@ function getDataStrategyMatch( // doesn't have a `loader` to run and no `lazy` to add one, then we can // just return undefined from the "loader" here let isMiddlewareOnlyRoute = - middleware && middleware.length > 0 && loader == null && !lazy; + middleware && middleware.length > 0 && !loader && !lazy; if (callHandler && !isMiddlewareOnlyRoute) { return callLoaderOrAction({