Hands-On: Deploy a React App to Vercel
What We Are Building
A Vite React app deployed to Vercel with:
- Automatic production deploys on every push to
main - A preview URL for every pull request
- An environment variable that differs between preview and production
- A custom domain with automatic SSL
Total hands-on time is about 20 minutes, and everything here is on the free Hobby plan.
Step 1 — Push the App to GitHub
Any Vite React app works. If you need one:
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run build # confirm the build passes locally first
Add a component that reads an environment variable, so we can watch it change between environments:
// src/App.jsx
function App() {
return (
<main>
<h1>My React App</h1>
<p>API endpoint: {import.meta.env.VITE_API_URL}</p>
<p>Environment: {import.meta.env.MODE}</p>
</main>
);
}
export default App;
Push it:
git init && git add -A && git commit -m "Initial app"
gh repo create my-react-app --public --source=. --push
Step 2 — Import the Repo into Vercel
- Sign in at vercel.com with your GitHub account
- Click Add New → Project
- Vercel lists your repositories — click Import next to
my-react-app - Vercel detects Vite and pre-fills
vite buildand thedistoutput directory — leave them alone - Do not click Deploy yet — expand Environment Variables first (next step)
The first time through, GitHub asks you to install the Vercel app and grant access to either all repos or selected ones. I grant per-repo access — least privilege, and it keeps the import list clean.
Step 3 — Add Environment Variables
Still on the import screen, add:
| Key | Value | Environments |
|---|---|---|
VITE_API_URL | https://api.example.com | Production |
VITE_API_URL | https://staging-api.example.com | Preview |
The same key with different values per environment is the core pattern: PR builds hit staging, production builds hit the real API, and no code changes between them.
Two rules that save real pain later:
- Anything prefixed
VITE_ends up in the shipped JavaScript bundle. Never put secrets there — API keys belong in serverless functions, not client env vars. - Env vars are baked in at build time for a static React app. Changing a value in the dashboard does nothing until the next deployment.
Now click Deploy. The build log streams live; in under a minute you get a URL like https://my-react-app-eta.vercel.app showing the production API endpoint.
Step 4 — Trigger a Preview Deployment
git checkout -b feature/new-heading
# change the h1 text in src/App.jsx
git commit -am "Update heading"
git push origin feature/new-heading
gh pr create --fill
Within a minute, the Vercel bot comments on the PR with a preview URL like:
https://my-react-app-git-feature-new-heading-yourname.vercel.app
Open it and check the page — it shows the staging API URL, because preview deployments get the Preview-scoped variables. The production URL still shows the old heading and the production API. Each subsequent push to the branch updates the same preview URL.
This is the review workflow in practice: a reviewer clicks the link and tests the exact build, instead of pulling the branch and running it locally.
Step 5 — Merge to Production
Merge the PR on GitHub. Vercel picks up the push to main and starts a production deployment automatically. Watch it under the project's Deployments tab — the new deployment is built, then the production domain is switched to it atomically. There is no moment where users see a half-deployed site.
If the new deploy is bad, open Deployments, find the previous production deployment, and click Instant Rollback — the domain points back at the old build in seconds, no rebuild required.
Step 6 — Add a Custom Domain
- Project → Settings → Domains → enter
myapp.com→ Add - Vercel shows the DNS records to create at your DNS provider:
Type: A Name: @ Value: 76.76.21.21
Type: CNAME Name: www Value: cname.vercel-dns.com
- Add those records where your DNS is hosted, then wait for verification (usually minutes, up to an hour)
Once verified, Vercel provisions a Let's Encrypt certificate automatically and renews it forever — no Certbot, no cron jobs. Set one of the two (apex or www) as the primary domain; Vercel 308-redirects the other to it, so you do not split traffic between duplicate hosts.
Check it:
curl -sI https://myapp.com | head -5
# HTTP/2 200
# server: Vercel
Step 7 — Optional: vercel.json for SPA Routing
Client-side routing (React Router) needs unknown paths to fall back to index.html. Vercel handles most Vite setups automatically, but if deep links 404, add vercel.json to the repo root:
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}
Commit and push — configuration deploys with the code, which is exactly where deployment config belongs.
Common Problems
Build passes locally, fails on Vercel. Almost always a case-sensitivity bug: macOS filesystems are case-insensitive, Vercel's Linux builders are not. import App from './app' works locally against App.jsx and fails in CI.
Env var changes not showing up. Redeploy after changing values — go to Deployments, latest deployment, Redeploy. Build-time variables require a rebuild.
Domain stuck on "Invalid Configuration". Some registrars auto-append the zone name; a CNAME value of cname.vercel-dns.com.myapp.com means the trailing dot or record form is wrong. Verify with dig www.myapp.com CNAME +short.