Deploy a React (Vite) app

Netlify, Cloudflare Pages, GitHub Pages or plain shared hosting - and the fixes for blank pages and 404s.

A React app created with Vite compiles down to plain HTML, CSS and JavaScript files. That means you don't need a Node server to host it - any static host will do, many of them free, and so will an ordinary shared hosting plan. This guide covers the build, four hosting options, and the three problems almost everyone hits.

This guide applies to single-page apps built with Vite (and works the same for Vue, Svelte and other Vite projects). If you're using Next.js with server rendering, you need a host that runs Node instead.

1. Build and preview

npm run build
npm run preview

npm run build writes the finished site to a dist folder: an index.html and an assets folder with hashed filenames like index-4f9c2a.js. npm run preview serves that folder locally - always check the built version, not just the dev server, before deploying.


2. Choose a host

Netlify or Cloudflare Pages (recommended)

Push your project to GitHub, create a new site in either dashboard, and connect the repository. Set the build command to npm run build and the output directory to dist. Every push to your main branch now builds and deploys automatically, with HTTPS and a global CDN included.

GitHub Pages

Free for public repositories. Your site is served from a subpath - https://yourname.github.io/your-repo/ - so set Vite's base path to match (see step 3), and deploy the dist folder using a GitHub Actions workflow; Vite's documentation includes a ready-made one.

Shared hosting

Upload the contents of dist - not the folder itself, and not your whole project - into public_html (or a subfolder) using the File Manager or SFTP. There's nothing to install; your host just serves the files.


3. Problem one: a blank page

You deploy, open the site, and see nothing. Open the browser console: if you see 404 errors for files under /assets/, the app is looking for its files in the wrong place. This happens when the site isn't at the root of the domain - a GitHub Pages subpath, or a subfolder on shared hosting.

Fix it by telling Vite where the app will live, in vite.config.js:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  base: "/your-repo/", // or "/myapp/" for a subfolder; leave as "/" for a domain root
});

Rebuild and redeploy. If you use React Router, also pass the same value as its basename.


4. Problem two: 404 when you refresh a page

With client-side routing, /about isn't a real file - React Router handles it in the browser. Clicking a link works, but refreshing or sharing that URL makes the server look for an about file and return a 404. The fix is to tell the host to serve index.html for any path that isn't a real file.

Netlify: create public/_redirects containing:

/*  /index.html  200

Cloudflare Pages: single-page apps are detected automatically when there's no 404.html in the output.

Shared hosting (Apache or LiteSpeed): add this .htaccess file next to index.html:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

(For a subfolder, change both / paths to /myapp/.) Putting .htaccess in Vite's public folder copies it into dist on every build, so you can't forget it.


5. Problem three: environment variables

Vite only exposes variables that start with VITE_, and it bakes their values into the JavaScript at build time:

// .env.production
VITE_API_URL=https://api.example.com
const res = await fetch(`${import.meta.env.VITE_API_URL}/notes`);

Two consequences: changing a value means rebuilding, and nothing in a VITE_ variable is secret - anyone can read it in the downloaded JavaScript. API keys that must stay private belong on a server, never in frontend code. On Netlify and Cloudflare Pages, set these variables in the site's settings so the build server can see them.


6. Caching

Because Vite puts a content hash in every asset filename, those files can be cached for a long time - a new build produces new names. index.html is the exception: it should be revalidated on every visit, or returning visitors may load an old version that points at deleted assets. Netlify and Cloudflare handle this for you; on shared hosting, a short cache time for HTML in .htaccess does the job.


Checklist before you share the link

  • Tested with npm run preview, not just npm run dev.
  • Correct base path if the site isn't at a domain root.
  • Refreshing a deep link (like /about) works.
  • No secrets in VITE_ variables.
  • HTTPS enabled, and the page title and favicon replaced from Vite's defaults.
All guides React primer