Deploying a React SPA to Vercel: Configuration That Actually Works

· 8 min read · DevOps & Cloud

Practical Vercel deployment guide for React SPAs covering routing, environment variables, security headers, and common configuration pitfalls.

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:

  1. Static files in /public
  2. Serverless functions in /api
  3. 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.