React HashRouter: how to avoid headaches

BrowserRouter ain't what you need (for simple client side rendered SPAs).

Posted by Rafael Malgor on April 09, 2020 · 6 mins read

If you are anything like me, after playing around with React and rendering components here and there you probably started wondering how someone would go about loading a component conditionally. Soon you realized that you can conditionally render your components using your everyday if, switch, ternary operator and what not. All is good for a while but then it falls into you oh man, wouldn't it be great to load components base on the URL🤔

Enter the Browser Router

After googling for a few seconds you found about BrowserRouter. You go through the tutorials and it seems simple enough, you end up with something similar to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import './App.css';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';

function App() {
return (
<Router basename="/tutorial">
    <NavbarComponent></NavbarComponent>
    <Switch>
        <Route path={`/users`}>
            <UsersComponent>
                </UserComponent>
        </Route>
        <Route path={`/posts`}>
            <PostsComponent></PostsComponent>
        </Route>
    </Switch>
</Router>
);
}

export default App;

Somewhere else in your app:

1
<a href="/posts">View Posts</a>

You have your Router that can switch between routes and you have links with relative paths to navigate around. You run npm start(or yarn start 🐱), the dev server fires up, you try your links and they work like a charm.

Until they don't

You have been coding your React app for while and you already have a bunch of features working, so you start looking into the deployment process. It seems simple enough, for instance, if you used Create-react-app you just run npm run build, which generates a /build folder with your compiled site. Then you copy the content to your server (apache, nginx, IIS), you browse to your newly deployed app, which loads without any issues, as you are about to celebrate, you try one of your links and you are met by a beautiful:

"Error: 404 not found"

What they didn't tell you on those BrowserRouter tutorials (though to be fair is rather obvious when you think about it) is that each time you click on one of your links the web browser will go to the server and ask for the corresponding route, for example http://example.com/posts, since there is nothing there on the server, it will of course reply with a 404. Why did it work on the React dev server you ask? Simple, the dev server is defaulting any route to /index.html, thus it doesn't matter where your links point to, it will always return /index.html, then locally on the web browser the BrowserRouter component kicks in, checks on the URL and does its trick.

The solution: enter HashRouter

If you look around on google for a solution you will probably find suggestions about configuring your HTTP Server to do what the dev server does, that is to redirect all the requests to /index.html. That works, but why do you need to go to the server to load a component when your SPA is already fully loaded on the browser? The answer is that you don't, what you really need is HashRouter.

The HashRouter component is included on the React Router library ready to be used and you barely need to change your code to use it. Basically you need to import HashRouter instead of BrowserRouter and prepend your links with # instead of /

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import './App.css';
import {HashRouter as Router, Switch, Route} from 'react-router-dom';

function App() {
return (
<Router basename="/tutorial">
    <NavbarComponent></NavbarComponent>
    <Switch>
        <Route path={`/users`}>
            <UsersComponent>
                </UserComponent>
        </Route>
        <Route path={`/posts`}>
            <PostsComponent></PostsComponent>
        </Route>
    </Switch>
</Router>
);
}

export default App;

Somewhere else in your app:

1
<a href="#posts">View Posts</a>

HashRouter uses fragment links (Wikipedia), this type of links by definition will not trigger an HTTP request on the browser, this way you can support navigation while keeping everything locally, and without any special setup on the server.