import { defineConfig, type Plugin } from "vite"; import react from "@vitejs/plugin-react"; // preloadInterFonts injects `` tags into the // production index.html for the latin subset of every Inter weight // the SPA imports (400/500/600/700). The font files come from // `@fontsource/inter` and are hashed by Vite into `dist/assets/`, // so the filenames aren't known until after the bundle is emitted — // hence a `transformIndexHtml` hook scoped to build mode that reads // `ctx.bundle` and matches the emitted woff2 names. // // Why latin only: the admin panel UI is English-only ASCII, so the // latin subset is what the very first paint actually needs. Preload- // ing every fontsource subset (latin, latin-ext, cyrillic, cyrillic- // ext, greek, greek-ext × 4 weights = 24 files) would just waste // bandwidth and starve more critical resources of priority. If the // UI ever needs to render non-ASCII glyphs at first paint, this list // can be extended. function preloadInterFonts(): Plugin { const WEIGHTS = ["400", "500", "600", "700"] as const; return { name: "preload-inter-fonts", apply: "build", transformIndexHtml: { order: "post", handler(html, ctx) { if (!ctx.bundle) return html; const tags: string[] = []; for (const weight of WEIGHTS) { const re = new RegExp(`inter-latin-${weight}-normal-[^/]+\\.woff2$`); const fileName = Object.keys(ctx.bundle).find((n) => re.test(n)); if (!fileName) continue; // `base: "/"` is used in this config (so deep BrowserRouter // URLs like `/users` resolve `assets/...` from the site root // rather than relative to the current path). Emit matching // `/` prefixes here so the preload hits the exact same URL // the browser would otherwise request from the fontsource // @font-face rule, deduplicating the fetch instead of // doubling it. tags.push( ``, ); } if (tags.length === 0) return html; return html.replace("", ` ${tags.join("\n ")}\n `); }, }, }; } export default defineConfig({ plugins: [react(), preloadInterFonts()], // Absolute base so the SPA's asset URLs resolve from the site root. // The Go backend serves index.html for every unknown path (BrowserRouter // deep-link fallback); with a relative base, navigating directly to e.g. // `/users` would make the embedded `./assets/index-XYZ.js` URL resolve // to `/assets/index-XYZ.js` only when sitting at the root — at any // multi-segment path it would resolve relative to that path and miss. base: "/", build: { target: "es2020", chunkSizeWarningLimit: 1500, }, });