Skip to content

Instantly share code, notes, and snippets.

@harrietty
Last active September 16, 2021 07:56
Show Gist options
  • Save harrietty/6847cc4ab07d97b3abc2077911c91615 to your computer and use it in GitHub Desktop.
Save harrietty/6847cc4ab07d97b3abc2077911c91615 to your computer and use it in GitHub Desktop.
Why do we need to use HashRouter instead of BrowserRouter when hosting on S3?

Why do we need to use HashRouter instead of BrowserRouter when hosting on S3?

Remember, React is a SPA - Single Page Application

As we know, the main idea of React Applications is that they are single page. There is just ONE HTML file, and ONE JavaScript file (although we cound split it up - more on that another day). This means that we do not have different physical files for different pages in our Application. E.g. we do not have index.html for the homepage, about.html for the About page, blog.html for the blog page. This was one of the key differences we discovered when we started using React, as opposed to when we were just using HTML. With React, all the content and logic is written in JavaScript and injected into a single HTML page.

At the end of the day, when we bundle our development code, these are the files we get:

  • index.html
  • bundle.js
  • Any .css files we have
  • Any other images, fonts, etc that the app requires

But although there are not multiple HTML pages, like with a "traditional" website, as we know, we can make it seem like there are multiple pages.

We've been using BrowserRouter in our classes and examples, which works by looking at the URL in your browser address bar and making sure that it keeps the UI in sync with the URL. In other words, the BrowserRouter is always looking for changes in the URL and when it sees a change, it will make sure that the right components are being rendered, so that the page changes with the URL.

We should be familiar with this basic example from the React Router Docs

This all works fine locally but there seems to be a problem when we want to release our application to the world using S3.

The full explanation of what's going on here touches on hosting and servers, which we haven't covered yet, so if this doesn't make 100% sense to everyone at the moment, that's fine. But if you're already thinking about hosting on S3, then perhaps this will make a little more sense.

A quick note on how servers traditionally work

Servers are just computers which are always connected to the internet and always ready to send a file to another computer when that file is requested. Just like on your personal computer, these files can live in folders.

In a traditional website you might have some files in this structure:

├── index.html
├── script.js
├── main.css
├── about
│   ├── index.html
├── blog
│   ├── index.html
├── contact
│   ├── index.html

Servers are generally configured so that when the root is requested, e.g. http://www.my-traditional-site.com, the index.html page is delivered.

They will also generally be configured so that when any path is requested, e.g. http://www.my-traditional-site.com/about, it will look for an index page within that directory to send, e.g. about/index.html

This is called Server Side Routing because the server is sending different files depending on different routes that are visited by the user.

Hosting our application

When hosting our app in production, you would need a server when our files will live. These files will be, as mentioned:

  • index.html
  • bundle.js
  • Any .css files we have
  • Any other images, fonts, etc that the app requires

As you can see, we do not have multiple folders or multiple index.html pages, because React is a SPA.

When a user types the URL of the application in the URL bar, e.g. http://www.my-single-page-app.com, the browser makes a request to the server where those files live for the index.html page. A traditionally configured server will then send a file called index.html, as expected.

The index.html page will be sent to the browser, and that page in turn makes a request for the bundle.js and any other CSS/assets it needs.

With the bundle.js delivered, the entire logic for the entire application has already been sent to the browser. All the logic related to React Router, for looking for changes in the URL and updating the UI accordingly, has been delivered. So from then on, the user can start clicking links and navigating around the site and React Router will figure out which UI to show. When you're navigating in a SPA, you're not making new requests to the server for new pages to show - React Router is doing all the magic of "changing" the page contents for you. This is called Client Side Routing because all the logic for what page/content to show is happening within the browser.

So far so good....

Common "Gotcha" with an SPA

The problem comes when the user doesn't go directly to the root of the site first of all. What happens if they type a URL with a path e.g. http://www.my-single-page-app.com/about in the address bar? When you hit "enter" you'd be making a request to the server for the http://www.my-single-page-app.com/about page.

In a traditionally configured server, as we saw above, the server would see the path portion of the URL (the /about) and would look in a folder called /about and send back probably the index.html page inside that folder. However, for our SPA, we don't have a folder called /about. Our server instead needs to send the one and only index.html page we have, and then subsequently the bundle.js.

So this is a bit of a gotcha, but if you are configuring your own server, that's generally no problem. You can configure it to say: "No matter what path is requested, always send the index.html page". And along with the index.html page, we get the bundle.js, and therefore all the logic required to perform client side routing.

And when we are running locally, we are using a local server called React Dev Server which, handily, is configured exactly like this. Yay!

Problems with S3

Amazon S3 is providing you with a place to store your files, which is also what we use a server for, but because S3 is more about file-storage than specifically hosting a web application, you are limited in what you can configure yourself. You can configure it so that when the browser requests bucket-name.s3.amazonaws.com the index.html page is returned, but it's not possible to say "No matter what path is requested, send the index.html page". So there we have a problem.

When the browser requests bucket-name.s3.amazonaws.com/about, S3 will want to send whatever is in the contents of the /about folder in S3 - which won't exist, because you've only got the files mentioned above (index.html, bundle.js etc) so it will send back a 404. This is the essential problem we are trying to work around.

Solutions to working with S3

There are actually two solutions to this.

  1. Configuration changes in S3 - which I won't go into now but feel free to ask me to explain these separately

  2. Use HashRouter instead of BrowserRouter

BrowserRouter expects to see changes to the path in the URL, such as:

  • bucket-name.s3.amazonaws.com/about
  • bucket-name.s3.amazonaws.com/contact
  • bucket-name.s3.amazonaws.com/blog

So you will configure routes in your app with the paths above.

Whereas HashRouter will look at anything in the URL which comes after a # sign, hence the name HashRouter. Such as:

  • bucket-name.s3.amazonaws.com/#about
  • bucket-name.s3.amazonaws.com/#contact
  • bucket-name.s3.amazonaws.com/#blog

The clever thing here is that anything that comes after a # is not a path. Remember how we used hashes for navigating within the same page to different HTML elements by ID?

So whether the user types any of these URLs in the address bar:

  • bucket-name.s3.amazonaws.com/#about
  • bucket-name.s3.amazonaws.com/#contact
  • bucket-name.s3.amazonaws.com/#blog

The server will only see the bucket-name.s3.amazonaws.com portion, and will send back the index.html page, and subsequently the bundle.js. HashRouter, however, does understand what those bits after the # mean, and will render the correct UI for each variation. Therefore, no matter what page the user goes to first in our app, they will have all the content and logic delivered by S3 with no problem, and React Router will be able to handle the client side routing - watching for URL changes and switch the UI accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment