1. Answers

Push state vs. hash-based routing: which to use?

    In the world of React.js single-page apps, there are two types of routing: push-state, and hash-based. Each of these have their strengths and weaknesses.

    Now as you may know, most of the React ecosystem focuses on push-state routing, using the HTML5 History API and (sometimes) server-side rendering with Node.js. There are a number of reasons for this, the two main ones being:

    • The HTML5 History API makes it possible to have multiple routes indexed by Google
    • Server-side rendering gives your app a few milliseconds faster load time on the first page load

    In contrast, hash-based routing works by only considering the part of the URL starting with the # character, which I’ll refer to as the hash. This vastly simplifies things for a few reasons:

    • The server never sees the hash, meaning it is handled 100% in-browser
    • Changes to the hash do not by default result in page reloads
    • Buttons and links are just plain old javascript-free <a> tags

    So let me just put this out there:

    In most cases, designing your new app with push-state routing is a mistake.

    Why?

    Firstly, Most apps are hidden behind a login screen, and thus have at most one page which needs to be indexed by Google. This means that the only benefit you can possibly receive from push-state is the performance improvement. And before you ask - no, removing #/ from your page URLs is not a benefit.

    Secondly, unless you’re Facebook, you probably shouldn’t be optimising for a few milliseconds speedup.

    And thirdly, unless you write your entire backend with server-side rendering in mind, you won’t get that performance improvement anyway.

    Maybe you’re an exception to these rules - but if you’re not, push-state routing adds enormous complexity for little to no benefit. In contrast, a roll-your-own hash-based router can be implemented in 23 lines of React:

    // Renders the application for the given route
    var Application = React.createClass({
      render: function() {
        switch (this.props.location[0])  {
        case '':
          return <div><h1>Index Page</h1></div>;
    
        default:
          return <div><h1>Not Found</h1></div>;
        }
      }
    });
    
    // Split location into `/` separated parts, then render `Application` with it
    function handleNewHash() {
      var location = window.location.hash.replace(/^#\/?|\/$/g, '').split('/');
      var application = <Application location={location} />;
      ReactDOM.render(application, document.getElementById('react-app'));
    }
    
    // Handle the initial route and browser navigation events
    handleNewHash()
    window.addEventListener('hashchange', handleNewHash, false);
    

    But James, you say - this looks much more complex than routing with react-router! Well, sure - it takes a few more lines within your application. But it also means you’ve got one less dependency to worry about. And in particular, one less dependency that has a history of changing its API to something beyond recognition.

    No don’t get me wrong - push-state routing does have its place. However, using it where it isn’t needed is effectively gold-plating your app - wasting precious resources on unneeded features when what you really want is to just get the job done.

    So next time you’re designing a new app, don’t shoot yourself in the foot; design your routing based on requirements, not trends.

    Join React Armory,
    Get Cool Stuff

    Exclusive access to resources.

    Early access to new content.

    No spam, ever.

    React Armory is growing. Join in to be the first to experience new lessons, examples and resources.

    Further Reading...