From f7482942a159461c51c79ad93f9b3abfce47eaec Mon Sep 17 00:00:00 2001 From: evanpelle Date: Fri, 24 Apr 2026 09:21:50 -0600 Subject: [PATCH] Fix proprietary asset serving in dev (#3662 regression) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The proprietary/ fallback middleware was registered with `return () => {}`, which placed it after Vite's htmlFallbackMiddleware. Browser `` requests carry `*/*` in their Accept header, so htmlFallback intercepted them and returned index.html — meaning OpenFrontLogo.svg, Favicon.svg, etc. silently rendered as the HTML shell. Register the middleware up front, check resources/ first so publicDir retains precedence, and set Content-Type via mrmime so browsers actually render the SVGs. --- vite.config.ts | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 4355badbe..3e7c48275 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,6 @@ import tailwindcss from "@tailwindcss/vite"; import fs from "fs"; +import { lookup as lookupMime } from "mrmime"; import path from "path"; import { fileURLToPath } from "url"; import { defineConfig, loadEnv, type Plugin } from "vite"; @@ -19,31 +20,30 @@ import { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -function serveProprietaryDir(dir: string): Plugin { - const resolvedDir = path.resolve(dir) + path.sep; +function serveProprietaryDir( + proprietaryDir: string, + resourcesDir: string, +): Plugin { return { name: "serve-proprietary-dir", configureServer(server) { - // Return a function so the middleware is registered after Vite's internal - // static-file handler (publicDir). This makes proprietary/ a fallback - // rather than taking precedence over resources/. - return () => { - server.middlewares.use((req, res, next) => { - if (!req.url) return next(); - const urlPath = new URL(req.url, "http://localhost").pathname; - const filePath = path.resolve( - dir, - decodeURIComponent(urlPath).replace(/^\//, ""), - ); - if (!filePath.startsWith(resolvedDir)) return next(); - if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { - res.setHeader("Cache-Control", "no-cache"); - fs.createReadStream(filePath).pipe(res); - } else { - next(); - } - }); - }; + // Must run before Vite's htmlFallback; skip when resources/ has the file + // so publicDir keeps precedence. + server.middlewares.use((req, res, next) => { + if (!req.url) return next(); + const rel = decodeURIComponent( + new URL(req.url, "http://x").pathname, + ).replace(/^\//, ""); + if (rel.includes("..")) return next(); + if (fs.existsSync(path.join(resourcesDir, rel))) return next(); + const filePath = path.join(proprietaryDir, rel); + if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) + return next(); + const mime = lookupMime(filePath); + if (mime) res.setHeader("Content-Type", mime); + res.setHeader("Cache-Control", "no-store"); + fs.createReadStream(filePath).pipe(res); + }); }, }; } @@ -123,7 +123,9 @@ export default defineConfig(({ mode }) => { plugins: [ tsconfigPaths(), - ...(!isProduction ? [serveProprietaryDir(proprietaryDir)] : []), + ...(!isProduction + ? [serveProprietaryDir(proprietaryDir, resourcesDir)] + : []), ...(isProduction ? [] : [