diff --git a/docs/config/index.md b/docs/config/index.md index 1c61f2bef5b7ac..6973797db4895c 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -462,6 +462,13 @@ export default defineConfig(({ command, mode }) => { `envPrefix` should not be set as `''`, which will expose all your env variables and cause unexpected leaking of of sensitive information. Vite will throw error when detecting `''`. ::: +### spa + +- **Type:** `boolean` +- **Default:** `true` + + Whether your application is a Single Page Application (SPA). Set to `false` for other kinds of apps like MPAs. Learn more in Vite's [SSR guide](/guide/ssr#vite-cli). + ## Server Options ### server.host diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index 319b461022433d..dbbf255c20005b 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -264,3 +264,14 @@ In some cases like `webworker` runtimes, you might want to bundle your SSR build - Treat all dependencies as `noExternal` - Throw an error if any Node.js built-ins are imported + +## Vite CLI + +The CLI commands `$ vite dev` and `$ vite preview` can also be used for SSR apps: + +1. Add your SSR middleware to the development server with [`configureServer`](/guide/api-plugin#configureserver) and to the preview server with [`configurePreviewServer`](/guide/api-plugin#configurepreviewserver). + :::tip Note + Use a post hook so that your SSR middleware runs _after_ Vite's middlewares. + ::: + +2. Set `config.spa` to `false`. This switches the development and preview server from SPA mode to SSR/MPA mode. diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 02ce73c99a58c8..73cc6899815a89 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -204,6 +204,12 @@ export interface UserConfig { 'plugins' | 'input' | 'onwarn' | 'preserveEntrySignatures' > } + /** + * Whether your application is a Single Page Application (SPA). Set to `false` + * for other kinds of apps like MPAs. + * @default true + */ + spa?: boolean } export interface ExperimentalOptions { @@ -270,6 +276,7 @@ export type ResolvedConfig = Readonly< /** @internal */ packageCache: PackageCache worker: ResolveWorkerOptions + spa: boolean } > @@ -510,7 +517,8 @@ export async function resolveConfig( ...optimizeDeps.esbuildOptions } }, - worker: resolvedWorkerOptions + worker: resolvedWorkerOptions, + spa: config.spa ?? true } // flat config.worker.plugin diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index e66733d5c971b6..a8cb533e35ad19 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -104,7 +104,7 @@ export async function preview( sirv(distDir, { etag: true, dev: true, - single: true + single: config.spa }) ) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 4dafb7fc99b335..c191aa5a0991a2 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -503,8 +503,10 @@ export async function createServer( middlewares.use(serveRawFsMiddleware(server)) middlewares.use(serveStaticMiddleware(root, server)) + const isMiddlewareMode = middlewareMode && middlewareMode !== 'html' + // spa fallback - if (!middlewareMode || middlewareMode === 'html') { + if (config.spa && !isMiddlewareMode) { middlewares.use(spaFallbackMiddleware(root)) } @@ -513,9 +515,12 @@ export async function createServer( // serve custom content instead of index.html. postHooks.forEach((fn) => fn && fn()) - if (!middlewareMode || middlewareMode === 'html') { + if (config.spa && !isMiddlewareMode) { // transform index.html middlewares.use(indexHtmlMiddleware(server)) + } + + if (!isMiddlewareMode) { // handle 404s // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...` middlewares.use(function vite404Middleware(_, res) {