Deployment
This guide covers the three most common deployment targets for Jumbo React Vite. All deployment paths start with the same production build command.
Building for production
pnpm buildThis runs tsc && vite build. TypeScript type errors will fail the build. Output is written to
the dist/ directory.
To preview the production build locally before deploying:
pnpm preview
# Serves dist/ at http://localhost:4173Option 1 — Vercel (recommended)
Vercel is the recommended deployment target. A vercel.json config ships with the project that
handles SPA routing and sets long-cache headers for built assets:
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
],
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}
]
}Install the Vercel CLI and deploy
bashnpm install -g vercel vercelVercel auto-detects Vite and sets the build command to
pnpm buildand output todist/.Set environment variables
Go to Project → Settings → Environment Variables in the Vercel dashboard and add any
VITE_-prefixed variables your app requires (e.g.VITE_GOOGLE_MAPS_API_KEY).Deploy to production
bashvercel --prod
Vercel creates a unique preview URL for every Git branch and pull request automatically. Use these to review and QA UI changes before merging to your main branch.
Option 2 — Docker + nginx
Use a multi-stage Dockerfile to build and serve the app with nginx:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# Stage 2: Serve
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]The nginx config must send index.html for all routes to support client-side routing:
# nginx.conf
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Build and run:
docker build -t jumbo-react-vite:latest .
docker run -p 8080:80 jumbo-react-vite:latestOption 3 — Static hosting (S3 + CloudFront)
# Build
pnpm build
# Upload to S3
aws s3 sync dist/ s3://your-bucket-name --delete
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/*"Configure the S3 bucket or CloudFront distribution to serve index.html for all 404 responses.
In CloudFront, add a custom error response: 404 → /index.html, HTTP 200.
Environment variables in CI/CD
For GitHub Actions, set secrets in Settings → Secrets and inject them at build time:
# .github/workflows/deploy.yml
- name: Build
env:
VITE_GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
run: pnpm buildThe Google Maps API key in src/components/maps/MapProvider/MapProvider.tsx is currently
hardcoded. Before any public deployment, move it to import.meta.env.VITE_GOOGLE_MAPS_API_KEY,
restrict the key to your production domain via the Google Cloud Console, and add the variable
to your deployment environment.
Post-deployment checklist
- All
VITE_environment variables are set in the deployment environment - SPA fallback (
try_filesor/*rewrite) is configured for client-side routing - HTTPS is enforced
- The Google Maps API key is replaced with an env variable and domain-restricted
-
pnpm buildcompletes without TypeScript errors locally before pushing - Login with
demo@example.comis disabled or replaced with real auth - Browser console shows no errors on first load
- Dark mode, RTL, and language switching work correctly