React Router and GitHub Pages

Christopher Dent
6 min readOct 28, 2021

My skin is crawling even typing that title since this combination has given me so much trouble, ever since I graduated coding school! My capstone project, DinoFinder2020, was, of course, a React Redux app. Locally it worked brilliantly and, once I eventually got it launched, it worked well enough on GitHub pages (with the backend on Heroku), well enough until you clicked ‘reload’ or ‘back’ or ‘forward’ using the browser navigation buttons. Of course that’s because in a React Single Page App (SPA) it’s well, a single page, it just looks like you’re traversing multiple pages as you navigate the app. With christopherdent.github.io/dinofinder-frontend as the root directory, as far as the internet itself is concerned, that’s all that matters, that’s the page. christopherdent.github.io/dinofinder-frontend/triassic isn’t a real page. You can see it in your browser address bar, but it’s not a real page, it’s being rendered for you by React Router. When it came to DinoFinder, after hours of tinkering I did solve that problem using BrowserRouter and modifying some of the routes, this was a while back and I don’t remember exactly what I did, but it wasn’t that big a deal. But just last night I launched my brand new portfolio site, looking beautiful and working well locally, and was horrified to see that upon launch, no internal links were working at all.

I Googled the problem as I did in the past and found various buts advice, usually pointing to using HashRouter, which I don’t like because of the ridiculously log URLs it produces. I was using BrowserRouter for DinoFinder, why the heck wouldn’t it work with my new portfolio site? I even literally copied and pasted all of the router logic from DinoFinder, changing only the component names and nav links, redeployed it and it still didn’t work. 404’s all over the place. I could not understand why. Honestly, I still don’t completely understand why it works in DinoFinder without the following tweaks, and not in my other React apps, but after re-reading the React Router docs I believe it’s got something to do with Redux. DinoFinder after all is a Redux app and my portfolio site is not. The most common use for using the low-level <Router> is to synchronize a custom history with a state management library like Redux and t seems that’s exactly what I did with DinoFinder. The portfolio, not being a Redux app, did not have this luxury.

It was a late night, but after hours of trial, error, and research, I got it.

Let’s start from the top here.

As per the create-react-app docs, GitHub pages doesn’t support routers that use the HTML5 pushState history API, and that includes React Router. This is because, like in my example above, when you click “Reload” on christopherdent.github.com/dinofinder/triassic, GitHub Pages has no idea what you’re talking about because it’s not a real page. These docs list two solutions, one being using HashRouter, which, as I mentioned, gives you ridiculously long and un-intuitive URLs and I’m not really OK with that. The other was a vaguely alluded to workaround to trick GiHub Pages to handle these 404 errors differently, by redirecting them to where they are actually supposed to go. A trick to make the router work properly on GitHub pages, eh?

I stumbled upon this page, in the comments section of another less than helpful guide, and I offer massive thanks to the author of this article. I’ll summarize the key points of what they explain, and I’ll show you some of my own code I integrated to make this happen for my portfolio site.

One thing the guide wasn’t clear on is that this is, in fact, a solution for using BrowserRouter with GitHub Pages. I read in many other places that BrowserRouter just doesn’t work on GitHub pages and I need to stick with HashRouter. Well, they were wrong. BrowserRouter can work. I imported it like so:

//App.jsimport {BrowserRouter as Router, Switch, Route } from 'react-router-dom'

Then I laid out my routes:

<Router basename="/christopher-dent"><Switch><Route exact path='/' component={Main} /><Route exact path='/christopher-dent' component={Main} /><Route path='/christopher-dent/index' component={Main} /><Route path='/christopher-dent/about' component={About} /><Route path='/christopher-dent/projects' component={Projects} /><Route path='/christopher-dent/blog' component={Blog} /></Switch></Router>

What you see above is essentially what was working for me just fine in DinoFinder (with Redux, remember), what should work in a React app in general. But not in GitHub pages, not with a plain old React app.

What next? Well, this is where I am super thankful to the author of the guide I linked to above because I wouldn’t have thought of this and it’s not as easy to find this tip as I would have expected — not in the docs (well, alluded to but not explained well) and hardly mentioned in any Medium blogs or Stack Overflow posts. It just involves a custom script to handle the 404 redirects and it really was simple to implement.

How, you ask?

  1. Create a file called 404.html in your public folder (same folder as index.html)
  2. Paste the following into 404.html (again, THANK YOU to the author of this script). Read the commented text too, it’s important.
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Single Page Apps for GitHub Pages</title><script type="text/javascript">// Single Page Apps for GitHub Pages// MIT License// https://github.com/rafgraph/spa-github-pages// This script takes the current url and converts the path and query// string into just a query string, and then redirects the browser// to the new url with only a query string and hash fragment,// e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes// https://www.foo.tld/?/one/two&a=b~and~c=d#qwe// Note: this 404.html file must be at least 512 bytes for it to work// with Internet Explorer (it is currently > 512 bytes)// If you're creating a Project Pages site and NOT using a custom domain,// then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).// This way the code will only replace the route part of the path, and not// the real directory in which the app resides, for example:// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes// https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe// Otherwise, leave pathSegmentsToKeep as 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);</script></head><body></body></html>

3. Throw this script into index.html, in the Head section

<script type="text/javascript">// Single Page Apps for GitHub Pages// MIT License// https://github.com/rafgraph/spa-github-pages// This script checks to see if a redirect is present in the query string,// converts it back into the correct url and adds it to the// browser's history using window.history.replaceState(...),// which won't cause the browser to attempt to load the new url.// When the single page app is loaded further down in this file,// the correct url will be waiting in the browser's history for// the single page app to route accordingly.(function(l) {if (l.search[1] === '/' ) {var decoded = l.search.slice(1).split('&').map(function(s) {return s.replace(/~and~/g, '&')}).join('?');window.history.replaceState(null, null,l.pathname.slice(0, -1) + decoded + l.hash);}}(window.location))</script>

I made it the very last thing before </head> but it shouldn’t really matter.

4. Re-deploy your app using react-gh-pages (also a huge thank you to the developer who built that). If you’re reading this, you’ve probably already got it installed and have tried deploying dozens of times already. But just in case, that link will explain how to use and install it.

Go to your app. Click on a link. What used to be a 404 is now your beautiful React component that you put so much time and effort into before hitting this little quandry. It just works, and that is that.

Conclusion

I hope this helps some poor soul someday who is in the same situation I found myself in last night. Feel free to get in touch if you have any questions about this… after all of this time I’m actually getting pretty comfortable with React Router.

Have a happy day.

C

--

--

Christopher Dent

student of code. var my_homes = [‘NY’, ‘MTL’, ‘DC’, ‘EDI’, ‘FL’, ‘?’]