fix(dev): route asset-extensioned and query-suffixed URLs through Nitro when a route matches#4277
Conversation
Asset-extensioned URLs (e.g. /api/photos/12345.jpg) were routed to Vite's static middleware instead of the matching Nitro handler. The ASSET_EXT_RE guard in nitroDevMiddlewarePre applied unconditionally to any matched route, including specific prefixed catch-alls like /api/photos/**. The guard was introduced (nitrojs#4234) to prevent a root-level renderer or user catch-all (/**) from swallowing Vite's own <script>/<link> serves on plain-HTTP non-loopback origins where Sec-Fetch-Dest is absent. Fix: narrow the guard so the asset-extension filter only applies when the matched Nitro route pattern is /** or /**:param (named root catch-all). Specific routes (/api/**, /api/photos/**) are never registered on paths Vite owns and should always win — restoring the behaviour from nitro@3.0.1-alpha.2. When the matched route's pattern is unreadable, default to false (let the route win) rather than falling back to the asset-extension guard. Closes nitrojs#4252
Prevent imports with Vite-specific query suffixes (?url, ?raw, ?inline, ?worker) from being externalized in the dev environment. Adds them to noExternal and intercepts them in fetchModule so they are processed by Vite asset plugins.
|
@harshagarwalnyu is attempting to deploy a commit to the Nitro Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughRefines Vite dev behavior: treat Vite query-suffixed imports as non-external and resolvable by the plugin container; change nitroDevMiddlewarePre to match Nitro routes by pathname so explicit Nitro routes win over Vite asset heuristics while root wildcards retain asset suppression. ChangesAsset-Extensioned Route Matching Fix
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR adjusts Vite dev-mode routing/externalization behavior so Vite query-suffixed imports (e.g. ?url) and Nitro wildcard routes interact correctly under baseURL, and adds regression coverage for the updated routing rules.
Changes:
- Add dev-env interception for query-suffixed module IDs so Vite asset plugins handle them rather than SSR externalization.
- Refine dev server routing so root-level wildcards don’t swallow Vite asset-extension requests, while prefixed splat Nitro routes can still win.
- Expand test coverage and add a root catch-all Nitro fixture route for routing regressions.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/vite/baseurl-dotted-param.test.ts | Updates/adds regression tests for Nitro vs Vite routing under baseURL (root wildcard vs prefixed splat behavior). |
| test/vite/baseurl-dotted-param-fixture/routes/[...path].ts | Adds a root wildcard Nitro route fixture used to validate wildcard routing behavior. |
| src/build/vite/env.ts | Extends the noExternal configuration to account for Vite query suffixes. |
| src/build/vite/dev.ts | Adjusts module fetching/externalization logic for query IDs and refines Nitro-vs-Vite routing decisions for wildcard routes. |
| // We also intercept imports with Vite-specific query suffixes (like ?url) | ||
| // to ensure they are handled by Vite's asset plugins instead of being externalized. | ||
| const hasQuery = id.includes("?"); | ||
| if ( | ||
| this.#preventExternalize && | ||
| (this.#preventExternalize || hasQuery) && | ||
| !id.startsWith("file://") && | ||
| importer && | ||
| id[0] !== "." && | ||
| id[0] !== "/" | ||
| (hasQuery || (id[0] !== "." && id[0] !== "/")) | ||
| ) { | ||
| const resolved = await this.pluginContainer.resolveId(id, importer); | ||
| if (resolved && !resolved.external) { | ||
| if (resolved && (!resolved.external || hasQuery)) { | ||
| return super.fetchModule(resolved.id, importer, options); | ||
| } | ||
| } |
There was a problem hiding this comment.
Fixed in 34ead56: extracted VITE_QUERY_RE = /\?(url|raw|inline|worker)(?:&|$)/ as a module-level constant and replaced hasQuery with hasViteQuery, keeping the behavior consistent with the noExternal pattern in env.ts.
|
Have you seen #4272 ? what is diffenet from #4266 (comment) ? |
|
Closing as
|
Problem
Two related regressions in
nitroDevMiddlewarePre(introduced around #4238):Asset-extension routing (Vite dev middleware swallows asset-extensioned URLs #4252, Regression - Sec-fetch-dest: image header causes 404 on API-served images (regression in 3.0.260429-beta) #4241): URLs with asset-like extensions (
.jpg,.png, …) are intercepted by Vite's static middleware even when an explicit Nitro route matches.<img src="/api/photos/12345.jpg">withsec-fetch-dest: imagereturns a 404 fromserve-staticinstead of reaching the handler.Vite query suffix externalization: Imports with Vite-specific query suffixes (
?url,?raw,?inline,?worker) in SSR environments are incorrectly externalized rather than being routed through Vite's asset plugin pipeline.Root Cause
Issue 1 (
configureViteDevServer):The current
nitroWinslogic applies the asset-extension guard uniformly to all Nitro routes. A root-level wildcard (/**) can legitimately swallow Vite's own<script src=".../entry.ts">requests, so it needs the guard. But a prefixed route like/api/photos/**never matches Vite-managed paths — the guard should not apply to it.Issue 2 (
FetchableDevEnvironment.fetchModule;env.ts):Imports with Vite query suffixes (
?urletc.) are treated as bare module imports and fall through to the external resolver, which marks them as external and bypasses Vite's asset plugins entirely.Fix
Routing fix
Extracts the matched route pattern from
nitroRouteMatchand checks whether it is a root wildcard (/**//**:). Only root wildcards get the asset-extension guard; all other explicit routes always win:Query import fix
?url|raw|inline|workertonoExternalso the bundler does not externalize these imports.fetchModule, detectshasQueryand forces query-suffixed imports throughresolveId→transformRequesteven whenpreventExternalizeis not set.Tests
Seven integration tests in
test/vite/baseurl-dotted-param.test.tscover:v1.2.3)sec-fetch-dest: image/api/**+.jpg)Accept: text/htmlnavigation overrideAll 7 pass.
pnpm fmtandpnpm typecheckclean.Related
sec-fetch-dest: imagecauses 404 on API-served images