Context
h3js/h3#1475 precomposes middleware chains: the global chain and each route's middleware+handler are composed once and cached, replacing per-request array merging and recursive dispatch (monomorphic call sites, ~10-18% faster dispatch, ~20% fewer allocations in isolated benchmarks). It also exposes composeMiddleware(middleware): (event, handler) => result as public API.
Nitro currently opts out of all of this: the ~getMiddleware override in src/build/virtual/app.ts rebuilds the middleware array on every request (route-rule middleware → global middleware → routed middleware → route middleware, 3-4 spreads) and h3 falls back to the recursive callMiddleware compat path whenever the override is present.
Proposal
Drop the ~getMiddleware override and let each source land where h3 already precomposes:
- Route-rule middleware + global middleware → register via
app.use() in the same order (route rules first). h3 precomposes the global chain automatically; nitro writes no composition code.
- Route middleware → keep passing
middleware in route options; h3 composes it with the handler per route on first match. Ordering matches today (runs after the global chain).
- Routed middleware (
middleware/ dir with path prefixes) — the one dynamic source. Keep the rou3 lookup but compose per matched node, cached on the match data (bounded by router nodes):
app.use((event, next) => {
const matched = findRoutedMiddleware(event.req.method, event.url.pathname);
if (matched.length === 0) return next();
const chain = (matched["~composed"] ??= composeMiddleware(matched.map((r) => r.data)));
return chain(event, next);
});
Notes
- Composed chains are static snapshots: HMR-mutated middleware must flow through
use()/route re-registration (which invalidate h3's caches) or an app rebuild, as dev mode already does.
- If the compat path is kept in the interim: h3 now resolves the dispatch strategy once per app (first request, re-checked after
use()/mount()), so the ~getMiddleware override must be assigned before the first request — nitro assigns at build time, so this already holds.
- h3 also has an internal
composeHandler(middleware, handler) (fixed terminal handler) that can be exported if useful here.
- End state: once nitro no longer overrides
~getMiddleware, h3 can consider removing it in v2 proper.
Requires a version of h3 including h3js/h3#1475.
🤖 Generated with Claude Code
Context
h3js/h3#1475 precomposes middleware chains: the global chain and each route's middleware+handler are composed once and cached, replacing per-request array merging and recursive dispatch (monomorphic call sites, ~10-18% faster dispatch, ~20% fewer allocations in isolated benchmarks). It also exposes
composeMiddleware(middleware): (event, handler) => resultas public API.Nitro currently opts out of all of this: the
~getMiddlewareoverride insrc/build/virtual/app.tsrebuilds the middleware array on every request (route-rule middleware → global middleware → routed middleware → route middleware, 3-4 spreads) and h3 falls back to the recursivecallMiddlewarecompat path whenever the override is present.Proposal
Drop the
~getMiddlewareoverride and let each source land where h3 already precomposes:app.use()in the same order (route rules first). h3 precomposes the global chain automatically; nitro writes no composition code.middlewarein route options; h3 composes it with the handler per route on first match. Ordering matches today (runs after the global chain).middleware/dir with path prefixes) — the one dynamic source. Keep the rou3 lookup but compose per matched node, cached on the match data (bounded by router nodes):Notes
use()/route re-registration (which invalidate h3's caches) or an app rebuild, as dev mode already does.use()/mount()), so the~getMiddlewareoverride must be assigned before the first request — nitro assigns at build time, so this already holds.composeHandler(middleware, handler)(fixed terminal handler) that can be exported if useful here.~getMiddleware, h3 can consider removing it in v2 proper.Requires a version of h3 including h3js/h3#1475.
🤖 Generated with Claude Code