[Data Mode] Throwing redirect in middleware isn't short-circuiting route module loading
Reproduction
https://codesandbox.io/p/devbox/snowy-resonance-2tmzs3?workspaceId=ws_EuPacS5TJ5ow37kc1WEv3V
Specifically highlighting the console log message coming from the component that should have been redirected away from instead of loading.
System Info
`react-router` 7.9.6
System:
OS: macOS 15.7.1
CPU: (12) arm64 Apple M3 Pro
Memory: 143.67 MB / 36.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.18.0
Yarn: 1.22.22
npm: 10.9.3
pnpm: 10.13.1
Browsers:
Chrome: 142.0.7444.162
Firefox: 145.0
Safari: 26.1
Used Package Manager
npm
Expected Behavior
From my read of how middleware works, I would expect that if one of the middleware functions in the chain on a route config throws a redirect at some point, that route's module and component shouldn't be loaded or rendered, and the routing should be short-circuited at that point (inferred by the recommendation for using this for Auth). So, I wouldn't expect any attempt to render the original destination for that route, and instead load the route module for the route that was the redirect's destination.
Actual Behavior
What I'm seeing instead, is that the original destination's route module is fully loaded and an initial render happens before the redirect kicks in and moves to the new destination. This presents a potential security risk and some UX concerns.
I noticed there was this bug fix that seems somewhat adjacent to this but in reverse for throws that don't originate from redirect. https://github.com/remix-run/react-router/pull/14182
This looks like a bug in the logic for determining how deep to render on initial hydration. It's a bit tricky because of the differences between SPA and SSR. The root issue at the moment is that the route doesn't have a loader, and therefore we decide the component is able to be rendered immediately because there's no loader data to wait on.
In a SPA, it does feel like if there's no loader but there is a middleware to run, we shouldn't display the component yet because it could redirect/throw an error, etc. like your example.
But in an SSR app if you had a similar setup with a clientMiddleware we would want to render the component because it would have already been server-rendered in the HTML so we'd get hydration errors if we chose to hold it behind clientMiddleware. The clientLoader.hydrate property sort of avoids this issue in an SSR app because that's the way to trigger client data functions on hydration. So you can't run clientMiddleware without a clientLoader, and thus we don't his this middleware w/o a loader scenario.
I'll look into a fix 👍
For now, the easiest workaround is to throw an empty loader on the route.
Thanks @brophdawg11! Appreciate the quick response 🙏 . Confirmed that adding a (no-op) loader function to our routes as a workaround does work
@brophdawg11 any news about this? Adding a noop loader didn't fix it for us