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.
Running the build
Section titled “Running the build”npm run buildVite creates a dist/ folder:
dist/ index.html assets/ index-[hash].js index-[hash].cssThe 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.
The React Router problem
Section titled “The React Router problem”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.
<!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.
The base path setting
Section titled “The base path setting”If your GitHub Pages repo is username.github.io/bulletin (not a custom domain), your app is served under a subpath. Tell Vite:
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.
Verifying the build locally
Section titled “Verifying the build locally”npm run previewServes 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.
Exercise
Section titled “Exercise”In your Bulletin frontend project:
- Run
npm run buildand inspect thedist/folder output. - Set
base: '/bulletin/'invite.config.ts(adjust to match your repo name). - Create
public/404.htmlwith the redirect script. - Add the decode script to
index.html. - Run
npm run previewand navigate directly tohttp://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 buildoutputs static files todist/with content-hashed filenames.- React Router requires
index.htmlto be served for every route — GitHub Pages doesn’t do this by default. - The
404.htmltrick redirects unknown paths back toindex.htmlwith the path encoded. - Set
baseinvite.config.tsto match the repo subpath on GitHub Pages.