Deploying a React SPA to Vercel: Configuration That Actually Works
You deploy your React app to Vercel, visit a route like /blog/my-post, and get a 404. You add a rewrite rule, but now your API routes break. You fix that, but now your sitemap returns HTML instead of XML. Vercel configuration has sharp edges that are not obvious until you hit them.
The Core Problem
A Single Page Application serves one HTML file for all routes. The React Router handles routing in the browser. But when a user navigates directly to /blog/my-post or refreshes the page, the server needs to return index.html — not look for a file at /blog/my-post.
vercel.json Configuration
{
"framework": "vite",
"buildCommand": "npm run build",
"outputDirectory": "dist",
"trailingSlash": false,
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}
The rewrite rule catches every URL and returns index.html. React Router then looks at the URL and renders the correct component.
trailingSlash: false prevents Vercel from redirecting /blog to /blog/. Without this, Google sees 308 redirects on every page, which counts as a "Page with redirect" issue in Search Console.
API Routes Priority
Vercel processes routes in this order:
- Static files in
/public - Serverless functions in
/api - Rewrite rules
This means /api/sitemap.xml is handled by your serverless function, not by the SPA rewrite. No additional configuration needed.
Environment Variables
Set environment variables in the Vercel dashboard, not in your code:
# Your .env.local (local development)
VITE_SUPABASE_URL=https://xxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGci...
In Vercel: Settings → Environment Variables. Variables prefixed with VITE_ are available in the browser bundle. Variables without the prefix are only available in serverless functions.
Headers for Security
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
]
},
{
"source": "/images/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}
]
}
Static assets in /images get aggressive caching (1 year). The hashed filenames from Vite ensure cache invalidation on content changes.
Preview Deployments
Every pull request gets a preview URL. Use this to test changes before merging:
https://your-project-abc123.vercel.app
Preview deployments use the same build process as production — they catch issues that local development misses.
Custom Domains
In Vercel: Settings → Domains. Add your domain and update DNS:
- A record:
76.76.21.21 - CNAME:
cname.vercel-dns.com
Vercel automatically provisions and renews SSL certificates.
Monitoring
Vercel provides built-in analytics:
- Web Vitals: LCP, FID, CLS scores per route
- Function logs: Serverless function execution times and errors
- Deploy logs: Build output and error messages
Takeaways
Vercel deployment for SPAs is straightforward once you understand the routing priority: static files, then API functions, then rewrites. The trailingSlash: false setting prevents SEO issues. Security headers and caching headers belong in vercel.json, not in your application code.