Client Side Routing on GitHub Pages with Create React App
So in my last blog post I talked about incorporating EmailJS in my portfolio website. While I had some questions, the process of doing that was actually fairly easy. On the other hand, when I deployed to GitHub Pages that’s when things got messy. The process of actually deploying was about as seamless and simple as it gets, but once I deployed I realized my client side routing was not working, and it took me a while to figure out how to fix it.
Deploying to GitHub Pages
If you already know how to deploy an application bootstrapped with Create React App to GitHub Pages, I suggest you skip to the next section about fixing your client side routing. If you don’t know, don’t worry because this is the easy part!
First Step
run either:
yarn add gh-pages
or
npm install --save gh-pages
Second Step
You need to add a homepage field in package.json
. According to the Create React App Docs, Create React App uses the homepage
field to determine the root URL in the built HTML file. If you didn’t a custom domain page by adding a CNAME file (which I didn’t) you’ll add something like this to the top of your package.json
:
"homepage": "https://your-github-username.github.io/your-project-repo-name"
If you want to have a custom domain page URL I would suggest following this article. After that, it’s as simple as using that URL in the homepage
field in your package.json
.
Third Step
You will need to add predeploy
and a deploy
script to package.json
.
"predeploy": "yarn run build",
"deploy": "gh-pages -d build",
or
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
Fourth Step
Simply run either:
yarn run deploy
or
npm run deploy
Final Step
Go to your project repo on GitHub, go to settings.
Scroll down to the GitHub Pages section.
In the green section you should see the URL that you have in your homepage
field of package.json
. In addition you want to make sure that the branch that is chosen is the gh-pages
branch that I have highlighted. If it’s not, select that branch and then save.
Client Side Routing
Alright, so you should be deployed on GitHub Pages now. Only problem is, when you navigate to your domain URL, you may be getting a 404 error. Or perhaps the domain URL is fine but when you navigate to different routes you get a 404 error.
I missed this very important advisory when reading through the Create React App Docs on Deployment:
GitHub Pages doesn’t support routers that use the HTML5
pushState
history API under the hood (for example, React Router usingbrowserHistory
). This is because when there is a fresh page load for a url likehttp://user.github.io/todomvc/todos/42
, where/todos/42
is a frontend route, the GitHub Pages server returns 404 because it knows nothing of/todos/42
.
I’m going to walk you through what I did to get my client side routing working. There is another way to do it using HashRouter
that I will not be going over in this blog.
Starting Out
So I used BrowserRouter
, and Switch
. At the top of my App.js
file I had import { BrowserRouter as Router, Switch, Route } from ‘react-router-dom’;
To start off my routes looked like this:
<Router> <Switch> // These could also look like:
// <Route exact path='/' component={Home}/>
<Route exact path='/'>
<Home/>
</Route> <Route exact path='/portfolio'>
<Portfolio/>
</Route> <Route exact path='/contact' >
<Contact/>
</Route> <Route exact path='/resume'>
<Resume/>
</Route> </Switch></Router>
First Step
So the first change I made was using PUBLIC_URL:
<Router basename={public.env.PUBLIC_URL}> <Switch> <Route exact path='/'>
<Home/>
</Route> <Route exact path='/portfolio'>
<Portfolio/>
</Route> <Route exact path='/contact' >
<Contact/>
</Route> <Route exact path='/resume'>
<Resume/>
</Route> </Switch></Router>
So what is PUBLIC_URL
? As usual you can get a good description of it from the Create React App Docs:
What is basename
doing? From the React Router Docs:
Without this step, /project-repo-name
would be treated as a pathname, rather than part of the base URL. So rather than navigating to https://github-username.github.io/project-repo-name/portfolio
it would try to navigate to https://github-username.github.io/portfolio
.
I still had one problem, though, if someone refreshed while they were on a route that wasn’t '/'
it would cause a 404 error.
Second Step
So why was I still getting 404 errors on refresh? Well it goes back to the fact that GitHub Pages is hosting your homepage URL but it knows nothing of your client side routing, it doesn’t recognize those routes as being part of the same application.
The solution lies in creating a 404.html
file and putting it in your public
folder. This file will include a script that converts the path into a query string then redirects to a URL that’s the query string and a hash fragment. GitHub Pages will ignore the query string and just return the index.html
file. In index.html
you’ll include a script that will check for a query string and then convert it to a URL that will be added to the browser’s history using window.history.replaceState()
, once the Single Page Application script (this loads in index.html
) is finished loading it will redirect to the correct URL and therefor the correct page.
If that sounds like a lot, don’t worry. The code for all of this is available here. All credit goes to Rafael Pedicini and anyone else who may have contributed to the repository. You will just need to take the code from the 404.html
file in that repository and put it in your own 404.html
file (remember to have this in the public folder). If you didn’t use a custom domain page URL you may need to change the value of pathSegmentsToKeep
to 1. Like so:
// this will start out equal to 0
var pathSegmentsToKeep = 1;var l = window.location;l.replace(l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') + l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' + l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') + (l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') + l.hash
);
NOTE: Take the full 404.html
file from the GitHub repo, not only is this not everything you need, but it lacks the proper attribution to the creator of this code.
Then you will need to copy the redirect script into your index.html
file. For me I had it inside<head>
underneath everything else. The documentation says the script has to be before the Single Page Application script, so if you have issues for some reason, I would try to move it up further in the <head>
.
Final Steps
Now all you should have to do is commit and push your changes to GitHub and then run either:
yarn run deploy
or
npm run deploy
Your client side routing should be working, and even if you refresh on any of the paths, they should load back up!
In Conclusion
I personally chose GitHub pages because I’ve experienced slow load times on Heroku. Though it took a minute to put all of the pieces together, no individual step was too difficult. Deploying to GitHub Pages is easy, and thanks to the repo created by Rafael Pedicini, it’s possible to get your client side routing working too. So GitHub Pages allows you to create a custom domain URL, it offers fast ( or at least faster) load times, and you can still get dynamic routing in your Single Page Application. It is worth noting that because of the way Google indexes pages, nothing but your homepage URL will get indexed by Google if you do your routing this way. The same is also true if you use HashRouter
to overcome the difficulty of client side routing on GitHub Pages. What do you think, is it worth it? Or would someone be better off using a hosting service that is designed to handle Single Page Applications?