React SPA Deployment Guide: CDN, Server, and PaaS Options
React single-page applications (SPAs) compile down to static files — HTML, CSS, and JavaScript bundles. Once built, they require no server-side execution, making them fast to deploy and cheap to host. But "static" doesn't mean simple: production deployments involve environment variables, build optimization, routing configuration, and CDN delivery.
This guide walks through deploying a React SPA to production on [PandaStack](https://pandastack.io).
How React Builds Work
When you run npm run build, Create React App (or Vite) compiles your application into a build/ or dist/ directory containing:
index.html— the shell pagestatic/js/— JavaScript bundles, hashed for cache bustingstatic/css/— compiled stylesheets- Other static assets (images, fonts)
This output is completely static — a web server just needs to serve these files. The browser downloads them and React handles routing client-side.
Setting Up pandastack.json
To deploy your React app on PandaStack, add a pandastack.json at your project root:
For Create React App:
{
"type": "static",
"buildCommand": "npm run build",
"outputDir": "build"
}For Vite:
{
"type": "static",
"buildCommand": "npm run build",
"outputDir": "dist"
}PandaStack reads this file, runs your build command, and serves the output directory from its global CDN. No Nginx configuration, no server setup.
Connecting GitHub for Automatic Deployments
Connect your GitHub repository from [dashboard.pandastack.io](https://dashboard.pandastack.io). Select your repository, choose the branch you want to deploy from (typically main), and PandaStack will automatically build and deploy every time you push.
# Also deployable from the CLI
npm install -g @pandastack/cli
panda deployAfter your first deploy, every git push is enough — no manual steps required.
Environment Variables in React
React environment variables must be set at build time, not runtime, because the browser cannot access server-side environment variables. Variables prefixed with REACT_APP_ (for CRA) or VITE_ (for Vite) are embedded into your JavaScript bundle during the build.
Set these in the PandaStack dashboard under your project's environment settings before triggering a build:
# Create React App variables
REACT_APP_API_URL=https://api.yourdomain.com
REACT_APP_GOOGLE_MAPS_KEY=your-maps-api-key
# Vite variables
VITE_API_URL=https://api.yourdomain.com
VITE_STRIPE_PUBLIC_KEY=pk_live_your-keyAccess them in your code:
// Create React App
const apiUrl = process.env.REACT_APP_API_URL
// Vite
const apiUrl = import.meta.env.VITE_API_URLImportant: Never put secrets (private keys, API secret keys) in REACT_APP_ or VITE_ variables. They are visible to anyone who opens your app in a browser.
Client-Side Routing
React Router uses browser history for navigation (e.g., /dashboard, /profile/settings). Static file servers serve files by exact path — a request to /dashboard would return a 404 unless the server is configured to fall back to index.html.
PandaStack handles this automatically for static deployments, routing all unmatched paths to index.html so React Router can take over client-side.
Optimizing Your Build
A few practices significantly improve React production builds:
// vite.config.js — production build optimizations
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
ui: ['@mui/material', '@emotion/react'],
}
}
}
}
})Manual chunking splits large libraries into separate files, improving cache efficiency. Users who return to your app after a code update don't re-download vendor libraries that haven't changed.
Connecting a Backend API
Your React SPA will typically communicate with a backend API. Deploy your API as a separate container project on PandaStack — one project for the React frontend (static), one for your Node.js/Django/FastAPI backend (container). They communicate over HTTPS.
// src/services/api.js
import axios from 'axios'
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
withCredentials: true,
})
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
export default apiProduction Checklist
pandastack.jsonhas correctoutputDir(buildordist)- All
REACT_APP_orVITE_variables set in dashboard before building - No secrets in client-side environment variables
- Client-side routing handled by PandaStack's SPA fallback
- Vendor chunks configured for better caching
- GitHub integration set up for automatic deploys
Testing Your Production Build Locally
Before pushing to production, test your build locally to catch broken environment variables or missing assets:
# Build the production bundle
npm run build
# Serve it locally with a static server
npx serve build -p 3000
# or for Vite
npx serve dist -p 3000Open http://localhost:3000 and verify all routes work, API calls succeed, and the console shows no errors. Testing locally with a production build catches issues that only appear outside development mode — such as missing REACT_APP_ variables or code that relied on React's development-only warnings.
Visit [docs.pandastack.io](https://docs.pandastack.io) for the complete deployment reference.