From 0d79a0ffd656fedbeac13098c2563074c3b17153 Mon Sep 17 00:00:00 2001 From: Yoshihiro OKUMURA Date: Mon, 14 Sep 2026 18:33:37 +0900 Subject: [PATCH] fix: serve data files with reserved characters in dev and preview sirv decodes request paths with decodeURI, which leaves %26 (&) and %23 (#) encoded, so data files with such characters in their names were not found by the dev and preview servers. Hand sirv the fully decoded path, the way Apache resolves it in production. --- vite.config.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index d3d618a..a3228ac 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,7 +20,34 @@ const staticContents = (): Plugin => { // browser revalidates against the ETag instead of falling back to // heuristic caching. Without it a re-dump, or another site served // from the same localhost port, is answered from a stale cache. - middlewares.use(sirv(dir, { dev, etag: true, maxAge: 0 })); + const serve = sirv(dir, { dev, etag: true, maxAge: 0 }); + middlewares.use((req, res, next) => { + // sirv decodes the path with decodeURI, which leaves reserved + // characters such as %26 (&) and %23 (#) encoded, so a file like + // `CFP&Reg.png` is not found. sirv reuses `req._parsedUrl` when + // it was parsed from the same `req.url`, so hand it the path + // decoded the way Apache resolves it. + const raw = req.url ?? '/'; + const idx = raw.search(/[?#]/); + const pathname = idx === -1 ? raw : raw.slice(0, idx); + if (pathname.includes('%')) { + try { + const decoded = pathname.split('/').map(decodeURIComponent).join('/'); + if (!decoded.includes('%')) { + (req as typeof req & { _parsedUrl?: object })._parsedUrl = { + pathname: decoded, + search: '', + query: undefined, + hash: undefined, + raw, + }; + } + } catch { + // leave malformed escapes to sirv + } + } + serve(req, res, next); + }); } }; return {