How to Deploy React App on GitHub Pages: Best Practices and Common Pitfalls
Deploying a React app on GitHub requires shipping a production-optimized build, configuring the homepage field in package.json, and implementing a 404.html fallback to handle client-side routing, while keeping sensitive environment variables out of the static bundle.
Deploying your first React application to GitHub Pages is a standard milestone for frontend developers, but the process involves specific architectural decisions documented throughout the facebook/react repository. While the React source code in packages/react/README.md emphasizes production build optimization, the repository's CI workflows in .github/workflows/runtime_build_and_test.yml demonstrate automation patterns that translate directly to deployment pipelines. This guide covers how to deploy React app on GitHub infrastructure using patterns derived from the React source code itself.
Ship a Production-Optimized Build
The React package documentation explicitly warns against deploying development builds to production. In packages/react/README.md, the maintainers note that development builds contain extra warnings and are significantly larger than production bundles, which impacts load times on GitHub Pages' free hosting tier.
Always generate a production build before deploying:
npm run build
# or
yarn build
This creates a build/ directory containing minified, static assets. According to the React repository's release process documented in scripts/release/README.md, these artifacts should be treated as disposable outputs rather than source code, meaning they should never be committed directly to your main branch.
Configure the Homepage Field
GitHub Pages serves React applications from a subdirectory path (e.g., https://username.github.io/repository-name/) unless you use a custom domain. Without proper configuration, your assets will attempt to load from the root domain, resulting in 404 errors for JavaScript and CSS files.
Add the homepage field to your package.json:
{
"name": "my-react-app",
"homepage": "https://username.github.io/my-react-app",
"scripts": {
"build": "react-scripts build",
"deploy": "gh-pages -d build"
}
}
This instructs the build tooling to prefix asset URLs correctly, ensuring that main.js loads from /my-react-app/static/js/main.js rather than /static/js/main.js.
Handle Client-Side Routing
GitHub Pages is a static file host, which creates a critical issue for single-page applications (SPAs) using React Router. When a user navigates directly to a client-side route (e.g., /about) or refreshes the page, GitHub Pages attempts to serve a file at that path. Since the route exists only in the React application's memory, the server returns a 404 error.
Implement a fallback mechanism by creating a 404.html file in your public/ directory:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script>
// Preserve the original path and redirect to index.html
const target = location.pathname + location.search + location.hash;
location.replace('/?redirect=' + encodeURIComponent(target));
</script>
</head>
<body></body>
</html>
Then modify your index.html or root component to check for the redirect parameter and use the HTML5 History API to restore the original path without reloading.
Secure Your Deployment
Environment variables used during the build process (e.g., REACT_APP_API_KEY) are baked directly into the static JavaScript bundle. Since GitHub Pages serves these files publicly, any secrets committed to the build will be exposed to anyone who inspects the page source.
Store sensitive values in GitHub Secrets rather than .env files, and inject them only in server-side processes or GitHub Actions workflows. If your application requires private API calls, implement a serverless function or proxy rather than calling the API directly from the React frontend.
Automate with GitHub Actions
The React repository demonstrates robust CI/CD patterns in .github/workflows/runtime_build_and_test.yml, which includes a yarn build step at lines 55-56 and bundle size validation via the sizebot job at lines 66-70. You can adapt these patterns to automate your GitHub Pages deployment.
Create .github/workflows/deploy.yml:
name: Deploy React to GitHub Pages
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build production bundle
run: yarn build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
This workflow mirrors the React repository's approach of generating build artifacts on each push rather than committing the build directory to version control.
Common Pitfalls to Avoid
| Pitfall | Why It Happens | How to Avoid |
|---|---|---|
| Forgetting the production build | Running npm start creates a development server with extra warnings and larger bundle sizes |
Always execute npm run build and deploy the resulting build/ directory |
Missing homepage configuration |
Asset paths default to root-relative URLs that break in subdirectory deployments | Add "homepage": "https://<user>.github.io/<repo>" to package.json |
| 404 errors on page refresh | GitHub Pages serves static files only and knows nothing about React Router routes | Implement a 404.html fallback that redirects to index.html with path preservation |
| Exposing API keys | Environment variables prefixed with REACT_APP_ are embedded in the static bundle |
Store secrets in GitHub Secrets and access them via server-side proxies only |
Committing build/ or node_modules/ |
Repository bloat and merge conflicts from generated files | Add both directories to .gitignore and use CI/CD to generate builds |
| Ignoring bundle size | Large JavaScript payloads cause slow loads on GitHub Pages' basic hosting | Enable code-splitting with React.lazy and monitor sizes with tools like sizebot |
Summary
Deploying a React application to GitHub Pages successfully requires attention to build optimization, routing configuration, and security practices derived from the facebook/react source code:
- Always generate production builds using
npm run buildbefore deploying, as development bundles contain extra warnings and inflated sizes documented inpackages/react/README.md - Configure the
homepagefield inpackage.jsonto ensure assets load correctly from subdirectory paths - Implement a
404.htmlfallback to handle client-side routing in single-page applications - Never commit API keys or the
build/directory; use GitHub Actions workflows inspired by.github/workflows/runtime_build_and_test.ymlto automate deployment - Monitor bundle size using CI checks similar to the React repository's
sizebotjob to ensure fast load times on GitHub Pages
Frequently Asked Questions
Why does my React app show a blank page after deploying to GitHub Pages?
This typically occurs when the homepage field is missing from package.json or when you deployed a development build instead of a production build. Without the homepage configuration, the browser attempts to load JavaScript and CSS files from the root domain rather than the repository subdirectory, resulting in 404 errors for all assets. Always run npm run build and ensure your package.json includes the correct GitHub Pages URL.
How do I fix 404 errors when refreshing a page on my deployed React app?
GitHub Pages serves only static files and has no knowledge of React Router's client-side routes, so direct navigation to paths like /about returns a 404. To resolve this, create a 404.html file in your public/ directory that redirects to index.html while preserving the original path, or configure your GitHub Action to generate a fallback page that uses JavaScript to rewrite the URL before the React app mounts.
Should I commit the build folder to my GitHub repository?
No, you should never commit the build/ directory to version control. The React repository's CI workflow in .github/workflows/runtime_build_and_test.yml demonstrates that build artifacts should be generated fresh during the deployment process rather than stored in the repository. Add build/ to your .gitignore file and use GitHub Actions to run npm run build automatically on each push, then deploy the generated files to the gh-pages branch without polluting your main branch history.
Can I use environment variables with GitHub Pages for API keys?
Environment variables prefixed with REACT_APP_ are embedded directly into the static JavaScript bundle during the build process, making them publicly visible to anyone who inspects your site's source code. For GitHub Pages deployments, you should never store API keys in your React code or repository files; instead, store sensitive values in GitHub Secrets and access them through serverless functions or proxy servers that keep the credentials on the backend.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →