Middleware should run even if there is no loader #12950
Replies: 5 comments 9 replies
-
I agree that middlewares should run regardless if the route has a loader or not, at least of routes behind the umbrella of the the middleware, so if you add a middleware in root every route will be under that middleware, but if you add it to a layout route other routes not nested inside should not trigger it. |
Beta Was this translation helpful? Give feedback.
-
Also at a minimum it would be super helpful to have the behaviour clearly documented (with most common use cases like logging and auth as examples), before middleware is deemed 'stable'. Also would be worth clarifying in the docs that a new context is instantiated on every invocation of the createRequestHandler (which is helpful, for example on Cloudflare Workers, for ensuring context data isn't shared between multiple requests to the same Worker process/isolate from different users). Maybe some kind of diagram would even be helpful for folks. It feels deserving of an entire page on the docs to explain all the aspects of Middleware so that folks can use it correctly and not have to dig through source code to understand how/when it is called and with what context. |
Beta Was this translation helpful? Give feedback.
-
@infiniteluke It sounds like you're using Data Mode ( We provide a little (not yet documented) helper that you can use to run middleware which will run even when no routes have loaders. This should avoid you needing to implement your own middleware logic. async function dataStrategy({ unstable_runClientMiddleware }) {
return unstable_runClientMiddleware(async ({ matches }) => {
const matchesToLoad = matches.filter((m) => m.shouldLoad);
let results: Record<string, DataStrategyResult> = {};
await Promise.all(
matchesToLoad.map(async (match) => {
results[match.route.id] = await match.resolve();
})
);
return results;
});
} See below for a separate comment on the reasoning behind the current logic - I commented here separately to give us a place to discuss the client-side-specific |
Beta Was this translation helpful? Give feedback.
-
Also reported in #13785. This is a great question and before we stabilize middleware we'll be sure to call out stuff like this clearly in the docs. I'm going to brain dump my thoughts here a bit on the current design and a jumping off point and we can continue to discuss if we think there ought to be any changes. This is mostly from a framework mode perspective, but generally speaking we try to maintain the same patterns across data/framework to avoid confusion - but there are certainly instances where the logic differs (i.e., Single Fetch). Middleware is coupled to Consider a root route that has a export const unstable_middleware: Route.unstable_MiddlewareFunction[] = [
async ({ request }, next) => {
let start = Date.now();
let res = await next();
console.log(request.method, request.url, `(${Date.now() - start}ms)`);
return res;
},
];
export function loader() {
return { version: getAppVersion() };
}
export function shouldRevalidate() {
return false;
} If I navigate between two child routes ( The same situation would apply if no loader existed on the root route. The way I think about it is that middleware is coupled to existing data requests made by your app:
In all of those cases, the middleware is sort of useless without the corresponding
I don't see middleware as wrapping (protecting) views in your app, but rather wrapping/protecting data (or loaders/actions). Maybe said another way - server middleware protects server data. If there is no data to protect, there is no need to run that middleware. If a route exists, and requires no data, then the only thing that route has is a JS file with the component in it and there's really no need to "protect" that because it's already public via the JS asset. Take the export default function Component() {
return (
<div>
<h3>Protected Docs</h3>
<p>This route should not be visible to unauthenticated users.</p>
</div>
);
} All of which will be publicly available in the JS assets for the route, so middleware doesn't even "protect" the view: ![]() It does provide a small DX enhancement to perform redirects for static routes like this, at the cost of a de-optimization. The current design allows you to retain the current |
Beta Was this translation helpful? Give feedback.
-
middleware depending on if a loader exists or not seems wrong to me. Middleware is a route concern, not a loader concern. If I want to use a redirects middleware at a route there's no reason to have a loader Or authentication, even if I don't load any data, maybe there are forms that submit to actions and I want them to login before they hit auth errors there |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
The soon to ship middleware feature is currently coupled to the need to run a loader/action. So, routes without a loader will not participate in middleware. This is problematic for routes that do not have a loader for many valid reasons. One example: I want to continue to use
useQuery
in a route or don't have time to migrate, but I still want to protect that route with an auth middleware.In my custom implementation of middleware in
dataStrategy
, I have to addloader: () => null
to routes that I want to "force" into the middleware matching. This is not obvious and is a confusing DX/API. It also creates cruft on a bunch of routes that haven't been converted to use loaders yet.Should middleware opt a route into the dataStrategy/revalidation?
Beta Was this translation helpful? Give feedback.
All reactions