You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Vite dev middleware treats Sec-Fetch-Dest: manifest requests as static assets and skips server routes #4284
In Vite dev mode with Nitro + TanStack Start, a server route such as /manifest.json works for a normal request, but fails when requested by the browser as a web app manifest.
The same behavior can be reproduced with another dynamic route such as /robots.txt: normal requests return 200, while the same URL with Sec-Fetch-Dest: manifest returns the Connect/Vite finalhandler 404.
Suspected Cause
The Nitro Vite dev middleware appears to classify any non-document Sec-Fetch-Dest request as an asset:
For Sec-Fetch-Dest: manifest, isAsset becomes true. The request is marked _nitroHandled, so the later Nitro middleware skips dispatching it to the SSR/server route environment. Since there is no static file in public, Vite/Connect eventually returns Cannot GET /manifest.json.
Expected Behavior
Server routes should still be allowed to handle requests such as Sec-Fetch-Dest: manifest when no static asset was served, or Nitro should avoid marking these requests as handled before the SSR/server-route dispatch path has a chance to run.
Static public/manifest.json is a valid workaround, but dynamic server routes for manifest-like resources should not be bypassed solely because of Sec-Fetch-Dest: manifest.
Reacted by Yudy Ananda, Floryan Simar and Alejandro Chinchilla
I did a deeper check with nitro@3.0.260522-beta. The issue still reproduces in this TanStack Start setup, and I think I found why the fix in 5b7e152bb5dce3e263da6b195c0b9e12e8895044 does not cover this case.
So the failure is still specifically triggered by Sec-Fetch-Dest: manifest.
I confirmed that 3.0.260522-beta does include the logic from 5b7e152bb5dce3e263da6b195c0b9e12e8895044. The installed package has the explicit route guard before the asset heuristic:
constmatch=nitro.routing.routes.match(...)constmatchedHandlers=match ? Array.isArray(match) ? match : [match] : []if(matchedHandlers.some((h)=>h?.route&&h.route!=="/**"&&!h.route.startsWith("/**:"))){returnnitroDevMiddleware(req,res,next)}
However, in this TanStack Start app, /manifest.json and /robots.txt are not present in nitro.routing.routes as explicit Nitro routes. I verified this by creating a Vite dev server in middleware mode with the same vite.config.ts and inspecting the initialized Nitro route table.
So, from Nitro's dev middleware point of view, this is not an explicit /manifest.json Nitro route. It is reached through the SSR renderer / catch-all route.
That means the new explicit route guard does not trigger, because the matched route is /**, which is explicitly excluded by the guard. Then this code path still applies:
For Sec-Fetch-Dest: manifest, isAsset becomes true, _nitroHandled is set, and the later Nitro middleware skips dispatching the request to the SSR/TanStack route environment. Since there is no static public/manifest.json, Vite/Connect returns Cannot GET /manifest.json.
So my current understanding is:
The commit fixes explicit Nitro routes.
This TanStack Start case still fails because /manifest.json is handled behind Nitro's /** SSR renderer, not as an explicit Nitro route.
The asset heuristic still runs before the SSR renderer has a chance to handle the request.
A possible fix might need to avoid marking Sec-Fetch-Dest: manifest requests as _nitroHandled before the SSR renderer/catch-all path can run, or otherwise allow SSR fallback when Vite did not actually serve a static asset.
Hi, I'm experiencing the same issue in the current version (3.0.260610-beta). I have a dynamic route in TanStack start for proxying images - without Nitro, the images are displaye correctly - with Nitro, I get a 404.
@Lars418 no estimate it is a draft pr pending to be validated by tanstack team or we might need to take an alternative approach. in the meantime you can simply use nitro api routes.
Fixed on main by #4467, which landed after the 3.0.260610-beta build tested here. Vite still gets first shot at asset-tagged requests, but when it has nothing to serve the request is now handed to the SSR/server routes instead of being dropped. I verified with a fresh dev-server repro: both /manifest.json and /robots.txt with Sec-Fetch-Dest: manifest return 200 from the catch-all. Please retry on a recent nightly.
Posted by an AI triage agent on behalf of a maintainer.
Confirming this is fixed in nightlyv3.0.260903-beta now. I have an API call where we do e.g. http://localhost:3556/api/branding/logo-f1bd5480-30cf-438d-be2e-209a29975c82.png which was failing in local dev earlier. It returned a 404 with body Cannot GET /api/branding/logo-f1bd5480-30cf-438d-be2e-209a29975c82.png.
With the nightly build, I can confirm it correctly returns a 200 with the actual image contents again. Thanks for the fix! 🚀
Activity
pi0 commentedon May 26, 2026
Can you try
v3.0.260522-betamissish commentedon Jun 3, 2026
Yes, that's what I'm using. The problem still exists in my DEV environment during testing.
missish commentedon Jun 3, 2026
I did a deeper check with
nitro@3.0.260522-beta. The issue still reproduces in this TanStack Start setup, and I think I found why the fix in5b7e152bb5dce3e263da6b195c0b9e12e8895044does not cover this case.Environment tested:
node:v24.15.0nitro:3.0.260522-betavite:8.0.16@tanstack/react-start:1.168.18@tanstack/react-router:1.170.10@tanstack/start-plugin-core:1.171.10Reproduction results:
I also checked these controls:
So the failure is still specifically triggered by
Sec-Fetch-Dest: manifest.I confirmed that
3.0.260522-betadoes include the logic from5b7e152bb5dce3e263da6b195c0b9e12e8895044. The installed package has the explicit route guard before the asset heuristic:However, in this TanStack Start app,
/manifest.jsonand/robots.txtare not present innitro.routing.routesas explicit Nitro routes. I verified this by creating a Vite dev server in middleware mode with the samevite.config.tsand inspecting the initialized Nitro route table.The route table was:
[ { "route": "/_nitro/tasks/**" }, { "route": "/**", "handler": "nitro/dist/runtime/internal/vite/ssr-renderer" } ]And both routes only matched the SSR catch-all:
The TanStack Start routes are defined as React Router file routes:
So, from Nitro's dev middleware point of view, this is not an explicit
/manifest.jsonNitro route. It is reached through the SSR renderer / catch-all route.That means the new explicit route guard does not trigger, because the matched route is
/**, which is explicitly excluded by the guard. Then this code path still applies:For
Sec-Fetch-Dest: manifest,isAssetbecomestrue,_nitroHandledis set, and the later Nitro middleware skips dispatching the request to the SSR/TanStack route environment. Since there is no staticpublic/manifest.json, Vite/Connect returnsCannot GET /manifest.json.So my current understanding is:
/manifest.jsonis handled behind Nitro's/**SSR renderer, not as an explicit Nitro route.A possible fix might need to avoid marking
Sec-Fetch-Dest: manifestrequests as_nitroHandledbefore the SSR renderer/catch-all path can run, or otherwise allow SSR fallback when Vite did not actually serve a static asset.Lars418 commentedon Jun 15, 2026
Hi, I'm experiencing the same issue in the current version (
3.0.260610-beta). I have a dynamic route in TanStack start for proxying images - without Nitro, the images are displaye correctly - with Nitro, I get a404.Is there currently a workaround?
pi0 commentedon Jun 15, 2026
I have proposed a (hacky) solution #4274 /cc @schiller-manuel
Lars418 commentedon Jun 16, 2026
Hey, that sounds great! Is there an estimated timeline when the bugfix will be available on npm?
pi0 commentedon Jun 16, 2026
@Lars418 no estimate it is a draft pr pending to be validated by tanstack team or we might need to take an alternative approach. in the meantime you can simply use nitro api routes.
pi0x commentedon Sep 7, 2026
Fixed on main by #4467, which landed after the
3.0.260610-betabuild tested here. Vite still gets first shot at asset-tagged requests, but when it has nothing to serve the request is now handed to the SSR/server routes instead of being dropped. I verified with a fresh dev-server repro: both/manifest.jsonand/robots.txtwithSec-Fetch-Dest: manifestreturn 200 from the catch-all. Please retry on a recent nightly.Posted by an AI triage agent on behalf of a maintainer.
dennisameling commentedon Sep 16, 2026
Confirming this is fixed in
nightlyv3.0.260903-beta now. I have an API call where we do e.g.http://localhost:3556/api/branding/logo-f1bd5480-30cf-438d-be2e-209a29975c82.pngwhich was failing in local dev earlier. It returned a 404 with bodyCannot GET /api/branding/logo-f1bd5480-30cf-438d-be2e-209a29975c82.png.With the nightly build, I can confirm it correctly returns a 200 with the actual image contents again. Thanks for the fix! 🚀