Let me preface this blog with the obvious - having a setup like this is a bad idea. If you have the choice, do not create your set up like this. Pick a winner and stick with it.

With that out of the way, this is the story of how adding a ‘Not found’ page in one of our Express.js applications took hours, and involved me learning way too much about routing in both Express and React, none of which actually helped.

We have an app. It started off as a React app with client-side routing. At one point we realised React was the wrong technology for our users (if you’re interested, I go in more detail here), so we started moving off it. There was no appetite to rewrite the React app as server side in one go, we had pretty much just released the final tweaks which broke the camel’s back. It did not make sense to continue building in React, so we switched to server-side routing and rendering. When I say switched, I don’t mean we switched, I mean we started using both.

Our express setup had something like this in it:

    app.use(routes())
    app.get('*', (req, res) => {
      res.sendFile(path.join(__dirname, '../build/index.html'))
    })

routes is a file that has all the server-side routes in it. So if a route does not match any of the server-side ones, we call the react application. That’s worked great so far and we haven’t had a problem

Except now we wanted a ‘Not found’ page. The internet suggests we should do something like the following in Express:

    // Handle 404
    app.use(function(req, res) {
      res.send('404: Page not Found', 404);
    });

This would work great if we didn’t already have a catch-all path to serve our React app. I tried the above and (as is obvious to me now) completely shut off the React side of the site. I don’t love that code, but I don’t dislike it that much.

You might be thinking, well, that’s fine, don’t define the 404 in Express, leave the server-side routing as is and let React handle it. Credit goes to my colleague Jamie for suggesting this to me.

I think this is a brilliant idea, and it will probably work for you. If it does, you should definitely do that. Define a catch-all route at the end of your React Router setup and let that handle the ‘Not found’ behaviour.

That’s apparently not how we roll. This is a shortened version of the last route in our React Router setup:

    <Route
        render={() => (
          <div className="content">
            <ScrollToTop>
              <Notifications />
              <Route
                render={({ location }) => {
                  if (config && config.googleAnalyticsId) {
                    ReactGA.pageview(location.pathname)
                  }
                  return (
                    <Header
                    />
                  )
                }}
              />
              <FooterContainer feedbackEmail={config.mailTo} />
            </ScrollToTop>
          </div>
        )}
      />

We already have a catch-all route. And it servers the layout, while all other routes populate the content within.

The only way we can do what we want is by letting one of the routers know about all of the paths, even ones it isn’t serving. This is not pretty, and it is not good, and it’s making the smell of technical debt stronger. And it works:

    app.get('/react-route-1*', (req, res) => {
      res.sendFile(path.join(__dirname, '../build/index.html'))
    })

    app.get('/react-route-2*', (req, res) => {
      res.sendFile(path.join(__dirname, '../build/index.html'))
    })

    app.get('/react-route-3*', (req, res) => {
      res.sendFile(path.join(__dirname, '../build/index.html'))
    })

    app.use('/react-route-4*', (req, res) => {
      res.sendFile(path.join(__dirname, '../build/index.html'))
    })

    app.get('/react-route-5*', (req, res) => {
      res.sendFile(path.join(__dirname, '../build/index.html'))
    })

    app.get('/react-route-6', (req, res) => {
      res.sendFile(path.join(__dirname, '../build/index.html'))
    })

    // This is the 404 logic
    app.use((req, res) => {
      // Handle 404 as needed
    })

I am definitely not proud of this. I hope you haven’t found this blog because you have the same problem. I really hope so.