SPA mode (ssr: false) generates a broken index.html — real shell ends up in __spa-fallback.html instead
When building with ssr: false in react-router.config.ts, index.html in build/client comes out broken — it's just a handful of stray <link> tags with no <html>, <body>, or script tags at all. The actual working SPA shell (correct doctype, modulepreloads, <Scripts />, everything) gets written to build/client/__spa-fallback.html instead, which nothing serves by default.
This means any static host serving index.html for / (Caddy/nginx/S3/etc, anything not aware of __spa-fallback.html) just serves a blank page.
Repro:
// react-router.config.ts
export default {
ssr: false,
} satisfies Config;
bun run build
# or: react-router build
Check build/client/index.html — it's ~300 bytes, only contains a few <link rel="icon">/<link rel="manifest"> tags from app/root.tsx's links() export. Compare to build/client/__spa-fallback.html in the same output — that one has the full correct shell.
I confirmed this is specifically tied to @react-router/[email protected] — bumping just the react-router package to 8.3.0 while keeping @react-router/[email protected] builds a correct index.html (still using v7's build path). Aligning @react-router/dev to 8.3.0 reproduces the broken output every time.
Versions:
react-router: 8.3.0@react-router/dev: 8.3.0vite: 7.3.6- node: v24.19.0, bun: 1.4.0
Expected: index.html should contain the full SPA shell (same content as __spa-fallback.html), like it did on 7.18.2.
Workaround: rolled back to [email protected] for now, where index.html builds correctly.
Source: remix-run/react-router