Fix res.render using the outer app's views for mounted sub-apps - #352
Merged
Merged
Conversation
…ounted sub-app Express resolves res.render() through this.req.app, which points at the mounted sub-app during its dispatch, so a sub-app's 'views', 'view engine' and engine() settings apply to its own routes. ultimate-express used this.app, which is fixed at response construction to the outer app, so a mounted sub-app's view configuration was ignored and rendering failed with a view lookup error. Three small pieces: - res.render now delegates to this.req.app.render(), matching express. - The optimized router path now swaps req.app to the mounted Application, mirroring what normal dispatch does, so req.app is consistent between the optimized and normal paths. - When an optimized path is exhausted and dispatch falls back to normal routing, req.app is restored to the top-level app, so a sub-app route calling next() into an outer handler doesn't leak the sub-app.
emilioastarita
marked this pull request as ready for review
July 23, 2026 19:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A mounted sub-app's
res.rendershould resolve views with the sub-app's ownviews/view engine/engine()settings — express does this by looking upthis.req.appinres.render, andreq.apppoints at the sub-app during its dispatch. ultimate-express instead callsthis.app.render(...), andres.appis fixed at response construction to the outer app, so a mounted sub-app's view configuration was ignored and rendering failed withFailed to lookup view ... in views directory <outer app's ./views>(orNo default engine was specifiedwhen the outer app has no view config).Repro
Verified side by side against express 4 and express 5.2.x.
Fix
res.rendernow delegates tothis.req.app.render(...), matching express._compileOptimizedRoutes) now records the mounted Application on the compiled mount route (mountApp) and_routeRequestswapsreq.appto it during dispatch — normal dispatch already swapsreq.appwhen it enters a mounted Application, but the compiled mount route has no callback to do so.req.appis restored to the top-level app, so a sub-app route that callsnext()into an outer handler doesn't leak the sub-app's config.This is rebased on top of the
refactor route optimizationcommit, and (2) is implemented in that new compiled-chain mechanism rather than the oldsetTimeout-based path.Tests
New
tests/tests/res/res-render-subapp.js(+ fixturetests/parts/subapp/index.ejs): a sub-app with its ownviewsdir mounted on an outer app with a differentviewsdir, rendering via the sub-app, plus a fallthrough case where the sub-appnext()s into an outer catch-all that renders with the outer app's config. Output matches express 4 and express 5; full suite passes (the only failing test,app-cluster.js, fails identically on a clean checkout here — an unrelated process holds port 3000).Context
Found integrating
@bull-board/express's sub-app (which sets its own views/engine) behind an outer app on a production Express 5-style stack.