Deploying React apps
Production builds, environment variables, SPA routing, and deploying to Vercel, Netlify and Firebase Hosting.
You've built something great — now let's put it on the internet. A Vite + React app compiles to static files (HTML, CSS, JavaScript and assets), so it can be hosted on any static host, often free, with HTTPS and a global CDN. This lesson covers building for production, environment variables, and deploying to Vercel, Netlify and Firebase Hosting.
Building for production#
Vite minifies your code, removes development-only checks, splits chunks and adds content hashes to file names (index-BqKa7F2x.js). The hash changes only when the file changes, so browsers can cache assets forever.
Always test the build locally before deploying:
If something works in npm run dev but breaks in preview, you've caught it before your users did.
Environment variables#
Different environments need different settings — an API URL for development, another for production. Vite loads them from .env files:
Read them with import.meta.env:
Key rules:
- Only variables prefixed with
VITE_are exposed to your code. Others stay private to the build. - Values are replaced at build time and end up as plain text in your JavaScript. Never put secrets (API keys with write access, database passwords) in
VITE_variables — anyone can read them in the browser. Keep secrets on a server. .env.productionis used byvite build;.env.developmentbyvite dev. Add.env.local(and.env.*.local) to.gitignorefor personal overrides.- Built-ins:
import.meta.env.MODE,DEV,PRODandBASE_URL. - Changing a variable requires a rebuild (and a dev-server restart).
On hosting platforms, set the same variables in the project's settings dashboard so they're available during the build.
The SPA fallback (don't skip this!)#
With React Router, /courses/react is handled by JavaScript in index.html. There's no courses/react file on the server, so refreshing that URL or opening a shared link gives a 404 — unless you tell the host to serve index.html for unknown paths. Each host has its own way, shown below.
Deploying to Vercel#
Option 1 — connect Git (recommended):
- Push your project to GitHub, GitLab or Bitbucket.
- Go to vercel.com → Add New… → Project and import the repository.
- Vercel detects Vite and fills in Build command
npm run buildand Output directorydist. - Add environment variables, then click Deploy.
Every push to main deploys to production, and every pull request gets its own preview URL — great for reviewing changes.
Option 2 — the CLI:
Add the SPA fallback with a vercel.json in the project root:
Real files (like /assets/index-BqKa7F2x.js) are still served first; only unmatched paths fall back to index.html.
Deploying to Netlify#
Connect your repository at app.netlify.com (Add new site → Import an existing project), or describe the build in a netlify.toml:
The status = 200 makes it a rewrite (the URL stays the same) rather than a redirect. Alternatively, put a _redirects file in public/ containing /* /index.html 200. Netlify also offers deploy previews and a CLI:
Deploying to Firebase Hosting#
Firebase Hosting is a fast, free-tier-friendly option, especially if you use other Firebase services (Auth, Firestore).
Answer the prompts:
This creates firebase.json:
Then build and deploy:
The headers block tells browsers to cache hashed assets for a year — safe, because a new build produces new file names. Use firebase hosting:channel:deploy preview for temporary preview URLs.
Deploying to a sub-path (e.g. GitHub Pages)#
If your app lives at https://user.github.io/my-app/, set Vite's base so asset URLs include the sub-path:
Pass the same value to your router: createBrowserRouter(routes, { basename: "/my-app" }). GitHub Pages has no rewrite support, so SPAs there usually need a 404.html copy of index.html or hash-based routing.
Automating with CI#
Run checks before every deploy so broken code never ships. A minimal GitHub Actions workflow:
Combine this with Vercel/Netlify Git integration: CI checks every pull request, and the host deploys when you merge.
Before you launch: a checklist#
-
npm run build && npm run previewworks, with no console errors - Refreshing on a deep link (e.g.
/courses/react) works — SPA fallback configured - Environment variables are set on the host; no secrets in
VITE_variables - A 404 page and a top-level error boundary exist
- Page
<title>and meta description are set - Custom domain connected, with HTTPS (all three hosts provide free certificates)
- Lighthouse scores checked for performance and accessibility
- Error monitoring (e.g. Sentry) and analytics are set up if you need them
Common mistakes#
- Forgetting the SPA rewrite, so deep links 404.
- Wrong output directory (
buildinstead of Vite'sdist). - Putting secrets in
VITE_variables, or expectingprocess.envto work in browser code. - Changing environment variables on the host but not redeploying.
- Committing
.env.localfiles with private values. - Testing only in dev mode and discovering production-only issues after launch.
What's next#
Static hosting is perfect for many apps. For SEO-critical pages, server rendering and full-stack features, the next step is a framework: Next.js and React Server Components.
Check your understanding
Quick quiz
1.In a Vite app, which environment variable can your React code read?
2.After deploying,
/courses/reactworks when you click a link, but refreshing gives a 404. Why?3.What does
npm run buildproduce in a Vite project?
Finished reading?
Mark this lesson complete to track your progress.