@@ -261,18 +261,28 @@ export async function configureViteDevServer(ctx: NitroPluginContext, server: Vi
261261 // (`routes/[...].ts` -> `/**`, `routes/[...slug].ts` -> `/**:slug`) is as authoritative as the
262262 // SSR `/**` and must not swallow Vite asset serves either, so both forms count as catch-all;
263263 // prefixed splat routes (`/api/photos/**`) are deterministic user routes and stay explicit.
264- const match = nitro . routing . routes . match (
265- req . method || "" ,
266- new URL ( withBase ( req . url ! , nitro . options . baseURL ) , "http://localhost" ) . pathname
267- ) ;
264+ const pathname = new URL ( withBase ( req . url ! , nitro . options . baseURL ) , "http://localhost" )
265+ . pathname ;
266+ const match = nitro . routing . routes . match ( req . method || "" , pathname ) ;
268267 const matchedHandlers = match ? ( Array . isArray ( match ) ? match : [ match ] ) : [ ] ;
269268 const isExplicitRoute = matchedHandlers . some (
270269 ( h ) => h ?. route && h . route !== "/**" && ! h . route . startsWith ( "/**:" )
271270 ) ;
272271
272+ // Public assets mounted under an explicit non-root `baseURL` without fallthrough are
273+ // authoritatively served by Nitro (a miss is a deterministic 404, never a Vite asset),
274+ // so they are as deterministic as an explicit route and route to Nitro the same way.
275+ const isExplicitPublicAsset = nitro . options . publicAssets . some (
276+ ( asset ) =>
277+ asset . baseURL &&
278+ asset . baseURL !== "/" &&
279+ ! asset . fallthrough &&
280+ ( pathname === asset . baseURL || pathname . startsWith ( asset . baseURL + "/" ) )
281+ ) ;
282+
273283 // An explicit user route is a deterministic match and always wins, regardless of how the
274284 // browser tags the request (#4108, #4241, #4252, #4270) — no heuristic may override it.
275- if ( isExplicitRoute ) {
285+ if ( isExplicitRoute || isExplicitPublicAsset ) {
276286 return nitroDevMiddleware ( req , res , next ) ;
277287 }
278288
0 commit comments