blog.karenying.com
blog.karenying.com

404: React Page Not Found

How to avoid the dreaded "Page Not Found" bug for apps using React Router on Netlify

Jul 2, 20205 min read

Netlify’s 404 PageNetlify’s 404 Page

You just deployed your first Create React App to Netlify. Site is live shows up in the scrolling deployment log. Success 🎉

You happily visit your new site and click around the relative links. Nice, React Router has been putting in the work. Everything looks great.

You enter a path in the address bar that you know is handled with React Router… and get hit with “Page Not Found”. Huh?

Convinced that you must’ve done something wrong, you spin up your app in your local dev environment and try to replicate the bug. But the routing is perfectly fine locally. It even works for the production version: npm run build and serve -s build. So what’s the problem here?

Routing in Single-Page Applications

If you used Create React App (CRA) and React Router, your site is most likely a single-page application (SPA).

As opposed to traditional multi-page applications, SPAs only maintain one HTML file, commonly index.html. Instead of serving a different HTML file for each path, SPAs depend on client-side routing. With almost 3 million weekly downloads, React Router is a popular client-side routing library.

In the simplest terms, React Router connects a path to a component. When the path is hit, React Router inserts the associated elements into the DOM tree, all without a page refresh.

However, for this process to work, the HTTP GET requests must only ever retrieve data from index.html — all the React Router logic is handled here. This means the server must direct all requests to index.html.

So why does this work locally? This is where Webpack comes in.

Webpack

Ever wonder what exactly happens when you run npm start in your CRA?

npm start runs react-scripts start under the hood (check it out in your package.json). If we peek into the react-scripts folder under /node_modules, we see that CRA uses Webpack to bundle and serve your app.

All of your app’s Webpack “settings” live in webpackDevServer.config.js and webpack.config.js under /react-scripts/config.

For the dev version of the app, Webpack sets the History API Fallback option as true. In webpackDevServer.config.js, Webpack is instructed to redirect all requests to paths.publicUrlOrPath which is the base url, http://localhost:3000/, that serves index.html:

*Examining *webpackDevServer.config.jsExamining webpackDevServer.config.js

For the prod version which is served when you npm run build and serve -s build, we turn to webpack.config.js. Here, we have the Navigate Fallback option set to paths.publicUrlOrPath + 'index.html':

*Examining webpack.config.js*Examining webpack.config.js

Voila! Again, Webpack directs all traffic to index.html.

Great, this explains why it’s all good in your local environment but what about on Netlify?

Netlify

Let’s go back to your production build on your local machine. When you run npm run build, Webpack bundles and minifies all your organized code into the build folder in your root directory. The contents of this folder are precisely what Webpack serves as the prod version locally, and what Netlify serves to the internet.

But have you ever tried serving your app with Python’s http.server? It requires an index.html file so we can run it in your prod build folder:

$ cd /build
$ python3 -m http.server

Head over to http://localhost:8000/. Enter a path that you specified in React Router or click on a relative link and hit refresh. You should experience deja vu and see this 404 error message:

Another 404 page 😞Another 404 page 😞

This is exactly what happens on Netlify.

Netlify’s servers and Python’s http.server do not handle redirects to index.html. Even though Netlify serves what Webpack bundled, it doesn’t use Webpack to actually serve the page.

Everything works perfectly if you don’t manually enter an address or refresh the page. This is because you aren’t making a new request for these two actions. You’re not navigating away from index.html and React Router is doing its thing correctly. However, the moment you refresh or enter a URL, the site breaks.

What can you do about this?

The Solution

Luckily, you can use Netlify’s Redirect options. To implement this, Netlify requires a file titled \redirects in the root directory. This one liner is where the magic happens:

/* /index.html 200

This rule specifies that all paths (/*) redirect to /index.html without changing the URL in the browser address bar (200).

You can either

  • put this _redirects file with the above line in the public folder of your app. Everything in this folder goes untouched into the root directory of /build.

OR

  • add && echo ‘/* /index.html 200’ | cat >build/_redirects to your build script in package.json. If you check the deployment logs, Netlify runs npm build before every deploy. This way, Netlify manually creates the _redirects file in the root directory every time.

With the addition of the _redirects file, we’ve replicated what Webpack does on your local machine.

Conclusion

You’ve just successfully fixed the dreaded 404 Netlify bug! In the process we learned about how React Router works for SPAs, Webpack configurations, and Netlify redirection rules.

Since this is a common problem, I found the fix easily by just Googling. It’s a one-liner after all. However, I couldn’t find a solid explanation for why it worked. Hopefully this post fills in that hole.

Netlify isn’t alone! As we saw above, Python’s http.server is another culprit. This sneaky bug happens with any statically hosted SPA. As an alternative to redirects, hash routing also allows SPA client-side routing. Definitely check it out if you’re interested.

Further Reading

Thanks for reading. Happy hacking!


Originally published in JavaScript In Plain English on Medium.