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/dev@8.3.0 — bumping just the react-router package to 8.3.0 while keeping @react-router/dev@7.x 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.0
vite: 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 react-router@7.18.2 for now, where index.html builds correctly.
When building with
ssr: falseinreact-router.config.ts,index.htmlinbuild/clientcomes 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 tobuild/client/__spa-fallback.htmlinstead, which nothing serves by default.This means any static host serving
index.htmlfor/(Caddy/nginx/S3/etc, anything not aware of__spa-fallback.html) just serves a blank page.Repro:
bun run build # or: react-router buildCheck
build/client/index.html— it's ~300 bytes, only contains a few<link rel="icon">/<link rel="manifest">tags fromapp/root.tsx'slinks()export. Compare tobuild/client/__spa-fallback.htmlin the same output — that one has the full correct shell.I confirmed this is specifically tied to
@react-router/dev@8.3.0— bumping just thereact-routerpackage to 8.3.0 while keeping@react-router/dev@7.xbuilds a correctindex.html(still using v7's build path). Aligning@react-router/devto 8.3.0 reproduces the broken output every time.Versions:
react-router: 8.3.0@react-router/dev: 8.3.0vite: 7.3.6Expected:
index.htmlshould contain the full SPA shell (same content as__spa-fallback.html), like it did on 7.18.2.Workaround: rolled back to
react-router@7.18.2for now, whereindex.htmlbuilds correctly.