Deploying React SPAs
Build, bundle, and deploy React single-page apps to static hosts with client-side routing and code splitting.
PlatformsBeginner
Why
React SPAs compile to static JS/CSS/HTML assets that any CDN can serve cheaply with global edge caching. No server runtime means predictable performance and minimal ops overhead.
Platform-as-a-Service adoption grew 24% in 2024 as teams shift from VM management to git-push deploys. the cloud infrastructure market reached $330B in 2024, growing 22% year-over-year (Synergy Research), and PaaS layers capture the majority of new workloads.
When
Choose this path for dashboards, admin panels, and highly interactive UIs without SSR needs. Avoid when SEO of first paint is critical — use Next.js/Remix instead.
How
1. Build — npm run build produces dist/ with hashed assets
2. Configure SPA fallback — all routes return index.html (rewrites)
3. Set base path — vite.config base or PUBLIC_URL for sub-path deploys
4. Deploy static — upload dist/ to Vercel/Netlify/S3+CloudFront
5. Cache strategy — immutable for hashed assets, must-revalidate for index.html
6. Env vars — inject at build time (REACT_APP_*) not runtime
Example
// vite.config.ts — SPA build config
export default defineConfig({
base: "/app/", // sub-path deploy
build: { outDir: "dist", sourcemap: false },
});
// vercel.json — SPA fallback rewrite
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
// netlify.toml
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
// S3 + CloudFront — set error document to index.html for SPA routing
Common Problems
- ⚠Blank page after deploy — forgot SPA fallback rewrite, deep links 404
- ⚠Stale assets after new deploy — index.html cached too aggressively, users get old JS chunks
- ⚠Env vars undefined in production — used process.env at runtime instead of build-time REACT_APP_ prefix
Best Practices
- ✓Cache hashed assets immutably (Cache-Control: max-age=31536000) and index.html with short TTL + must-revalidate
- ✓Use React.lazy + Suspense for route-level code splitting to keep initial bundle small
- ✓Set up health checks and graceful shutdown — handle SIGTERM to drain in-flight requests before exit