Skip to content

Commit 47d8af7

Browse files
committed
fix: execute _middleware.ts with path parameter when 404
1 parent 302b8bc commit 47d8af7

File tree

2 files changed

+67
-15
lines changed

2 files changed

+67
-15
lines changed

src/server/server.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ export const createApp = <E extends Env>(options: BaseServerOptions<E>): Hono<E>
7373
// Track requests that have been processed by each renderer to prevent double execution
7474
const processedRendererRequests = new WeakMap<MiddlewareHandler, WeakSet<Request>>()
7575

76+
// Track requests that have been processed by each middleware to prevent double execution
77+
const processedMiddlewareRequests = new WeakMap<MiddlewareHandler, WeakSet<Request>>()
78+
7679
// Share context by AsyncLocalStorage
7780
app.use(async function ShareContext(c, next) {
7881
await contextStorage.run(c, () => next())
@@ -174,6 +177,23 @@ export const createApp = <E extends Env>(options: BaseServerOptions<E>): Hono<E>
174177
)
175178
}
176179

180+
// Helper function to apply extra handlers for parent routing patterns like /:lang{en} or /:lang?
181+
const applyExtraHandlersForRouting = (handlers: MiddlewareHandler[]) => {
182+
const rootPath = dir.replace(rootRegExp, '')
183+
const isRootLevel = !rootPath.includes('/')
184+
const isSimpleStructure = !Object.keys(content).some((f) => f.includes('/'))
185+
186+
if (Object.keys(content).length > 0 && isRootLevel && isSimpleStructure) {
187+
subApp.use('/', ...handlers)
188+
Object.keys(content).forEach((filename) => {
189+
const path = filePathToPath(filename)
190+
if (path !== '/' && !path.includes('[') && !path.includes('*')) {
191+
subApp.use(path, ...handlers)
192+
}
193+
})
194+
}
195+
}
196+
177197
// Apply renderer middleware more robustly to handle all routing scenarios
178198
const rendererPaths = getPaths(dir, rendererList)
179199
rendererPaths.map((path) => {
@@ -202,19 +222,7 @@ export const createApp = <E extends Env>(options: BaseServerOptions<E>): Hono<E>
202222
subApp.use('*', wrappedRenderer)
203223

204224
// Apply extra middleware for parent routing patterns like /:lang{en} or /:lang?
205-
const rootPath = dir.replace(rootRegExp, '')
206-
const isRootLevel = !rootPath.includes('/')
207-
const isSimpleStructure = !Object.keys(content).some((f) => f.includes('/'))
208-
209-
if (Object.keys(content).length > 0 && isRootLevel && isSimpleStructure) {
210-
subApp.use('/', wrappedRenderer)
211-
Object.keys(content).forEach((filename) => {
212-
const path = filePathToPath(filename)
213-
if (path !== '/' && !path.includes('[') && !path.includes('*')) {
214-
subApp.use(path, wrappedRenderer)
215-
}
216-
})
217-
}
225+
applyExtraHandlersForRouting([wrappedRenderer])
218226
}
219227
})
220228

@@ -253,8 +261,28 @@ export const createApp = <E extends Env>(options: BaseServerOptions<E>): Hono<E>
253261
const shouldApply = middlewareDir === dir || dir.startsWith(middlewareDir + '/')
254262

255263
if (middleware.default && shouldApply) {
256-
// Use a dynamic route pattern that matches all paths including root
257-
subApp.use('/:*{.+}?', ...middleware.default)
264+
// Wrap each middleware to check if already processed
265+
const wrappedMiddleware = middleware.default.map((mw) => {
266+
// Get or create WeakSet for this specific middleware
267+
if (!processedMiddlewareRequests.has(mw)) {
268+
processedMiddlewareRequests.set(mw, new WeakSet<Request>())
269+
}
270+
const processedRequests = processedMiddlewareRequests.get(mw)!
271+
272+
return createMiddleware(async (c, next) => {
273+
if (!processedRequests.has(c.req.raw)) {
274+
processedRequests.add(c.req.raw)
275+
return mw(c, next)
276+
}
277+
return next()
278+
})
279+
})
280+
281+
// Use a wildcard pattern that matches all paths including root
282+
subApp.use('*', ...wrappedMiddleware)
283+
284+
// Apply extra middleware for parent routing patterns like /:lang{en} or /:lang?
285+
applyExtraHandlersForRouting(wrappedMiddleware)
258286

259287
// Track that this middleware has been applied to this directory
260288
if (!appliedMiddlewaresByDirectory.has(dir)) {

test-integration/apps.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,4 +1166,28 @@ describe('Renderer loading when mounted with special route patterns', () => {
11661166
})
11671167
})
11681168
})
1169+
1170+
describe('Path parameter pattern mounting (/:lang', () => {
1171+
const app = createApp({
1172+
root: '../mocks/app-with-mount-patterns/routes',
1173+
ROUTES: ROUTES as any,
1174+
RENDERER: RENDERER as any,
1175+
MIDDLEWARE: MIDDLEWARE as any,
1176+
})
1177+
1178+
describe('_middleware.ts execution when 404', () => {
1179+
const apps = new Hono()
1180+
apps.route('/:lang', app)
1181+
1182+
const paths = ['/bar/', '/bar/buzz']
1183+
1184+
paths.forEach((path) => {
1185+
test(path, async () => {
1186+
const res = await apps.request(path)
1187+
expect(res.status).toBe(404)
1188+
expect(res.headers.get('x-message')).toBe('from middleware')
1189+
})
1190+
})
1191+
})
1192+
})
11691193
})

0 commit comments

Comments
 (0)