Skip to content

Building for Production

npm run build compiles your React app into static files — HTML, JavaScript, and CSS — ready for any static host. This lesson covers the build output, the common React Router deployment problem, and how to fix it.

Terminal window
npm run build

Vite creates a dist/ folder:

dist/
index.html
assets/
index-[hash].js
index-[hash].css

The hash in the filenames is a content hash — it changes whenever the file content changes. This tells browsers to use cached files when nothing changed and fetch fresh files when it did. You should never rename or touch these files manually.

React Router handles routing in the browser. Your app’s router intercepts navigation and renders the right component — but this only works if the browser loads index.html first.

When a user visits https://you.github.io/bulletin/posts/42 directly (or refreshes), GitHub Pages looks for a file at bulletin/posts/42/index.html. It doesn’t exist, so GitHub Pages returns a 404.

The fix for GitHub Pages is the 404.html trick: create a public/404.html that redirects back to index.html with the path encoded in the query string.

public/404.html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script>
const path = window.location.pathname
const search = window.location.search
window.location.replace(
'/' + window.location.pathname.split('/')[1] +
'/?p=' + encodeURIComponent(path.slice(1) + search)
)
</script>
</head>
</html>

Then, in index.html, decode the redirect before React Router initializes:

<!-- index.html — add inside <head> -->
<script>
const p = new URLSearchParams(window.location.search).get('p')
if (p) {
history.replaceState(null, '', '/' + p)
}
</script>

This is a known workaround — GitHub Pages was not designed for SPAs, so client-side routing requires a redirect trick.

If your GitHub Pages repo is username.github.io/bulletin (not a custom domain), your app is served under a subpath. Tell Vite:

vite.config.ts
export default defineConfig({
base: '/bulletin/',
// ... other config
})

This prefixes all asset URLs with /bulletin/. Without it, the browser looks for /assets/index.js instead of /bulletin/assets/index.js and gets a 404.

Terminal window
npm run preview

Serves the dist/ folder at http://localhost:4173. Navigate to a post detail URL, then refresh — if the page loads correctly, the 404.html trick is working.

In your Bulletin frontend project:

  1. Run npm run build and inspect the dist/ folder output.
  2. Set base: '/bulletin/' in vite.config.ts (adjust to match your repo name).
  3. Create public/404.html with the redirect script.
  4. Add the decode script to index.html.
  5. Run npm run preview and navigate directly to http://localhost:4173/bulletin/posts/1. Confirm the page loads (it’ll show “Post not found” since you’re not hitting the real API — that’s fine; a 404 from the app, not the server, means routing is working).
  • npm run build outputs static files to dist/ with content-hashed filenames.
  • React Router requires index.html to be served for every route — GitHub Pages doesn’t do this by default.
  • The 404.html trick redirects unknown paths back to index.html with the path encoded.
  • Set base in vite.config.ts to match the repo subpath on GitHub Pages.