Vite plugin that lets every .html in src/pages behave like its own SPA entry during dev and build.
// vite.config.ts
import { defineConfig } from 'vite'
import viteMultiSpa from 'vite-multi-spa'
export default defineConfig({
plugins: [
viteMultiSpa({
pagesRoot: 'src/pages', // optional
// transformPageHtml: html => html, // optional, supports array
}),
],
})- Serves documents from
pagesRootwhen the browser requests/fooor/foo/. - Serves page-relative assets when requested from a page (based on the Referer header).
- Runs
transformPageHtmlonly on/index.htmland pages underpagesRoot. - Emits every
${pagesRoot}/**/*.htmlas a build chunk and flattens outputs to the root ofdist.
pagesRoot(string, defaultsrc/pages): Folder scanned for.htmlpages.transformPageHtml(IndexHtmlTransformHook | IndexHtmlTransformHook[]): Passed through to Vite and scoped to pages. See Vite's API documentation.redirects(Record<string, string>): Rewrite requests matching a pattern to a specific.htmlfile (resolved using thepagesRoot). Supports Cloudflare-style splats and placeholders.redirects: { '/admin/*': 'admin.html', '/users/:id': 'user.html', '/blog/*': 'blog/:splat.html', }
See the example branch, run pnpm install && pnpm dev, and you will observe src/pages/contact.html served in dev mode and emitted through the build so SPA behavior is obvious in both scenarios.
- How are my pages'
.htmlfiles processed? Exactly the same way Vite processes the defaultindex.html—script tags bundle and can contain ESM imports, relative assets resolve from the HTML file (e.g.<script type="module" src="main.tsx"></script>checksmain.tsxnext to that page), and everything else that works for Vite's root HTML works per-page here. - Are you open to supporting templating engines? Sure am; if you have ideas, feel free to write up a proposal, but please no unsolicited PRs for this feature.
- Does this work with Cloudflare's
@cloudflare/vite-plugin? You bet. I use those plugins together in production and made sure they play nicely.