2020-09-01T09:24:09Z

How to Deploy a React-Router + Flask Application

This is the third article in my "React + Flask" series, in which I discuss applications that combine a Flask API server with a React single-page application. This time I'm going to show you how to work with the popular React-Router library for React, and in particular how this library affects the production deployment of the application.

This is the third article in my "React + Flask" series. Make sure you read the first and second parts, as this part builds on the project built up to this point.

Introduction to React-Router

The React-Router library gives your React application the ability to implement client-side routes. Client-side routes can be used to change the contents of the page, or parts of it when a link is clicked. This is similar to how links allow you to navigate to different pages in a traditional application, but the navigation is entirely implemented in the client-side, without any trips to the server required.

The first step in adding React-Router to a React application is to install this library: There are two versions of the React-Router library for web and native applications. For this application you need to install the web version, which is called react-router-dom.

$ npm install react-router-dom

The React-Router library has four main components:

  • BrowserRouter: the top-level component that encloses all the routing support in your application.
  • Switch: the portion of the page that is subject to routing. The routes defined as children will be selected based on the current route.
  • Route: the definition of a client-side route.
  • Link: a link to a client-side route.

The general structure for an application that uses routing is as follows:

<BrowserRouter>
  <div>
    <!-- navigation bar with links to pages -->
    <Link to="/">Home</Link>
    <Link to="/page2">Page 2</Link>
  </div>
  <Switch>
    <Route exact path="/">
      <!-- contents for home page -->
    </Route>
    <Route path="/page2">
      <!-- contents for page 2 -->
    </Route>
  </Switch>
</BrowserRouter>

The URL matching done by React-Router is done in the order that routes are defined and is based on the start of the URL by default. For this reason, the "/" route uses the exact attribute, because if not it would match every URL. An alternative is to not use exact, but move the definition for this route to the bottom, and in that case it will act as a catch-all for any invalid client-side URLs, which sometimes is a desired feature.

If you have been following the small React + Flask project that I have been building in the previous parts of this series, you can now add a second page to it using the structure shown above. Here is the updated version of App.js:

import React, { useState, useEffect } from 'react';
import { BrowserRouter, Link, Switch, Route } from 'react-router-dom';
import logo from './logo.svg';
import './App.css';

function App() {
  const [currentTime, setCurrentTime] = useState(0);

  useEffect(() => {
    fetch('/api/time').then(res => res.json()).then(data => {
      setCurrentTime(data.time);
    });
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <BrowserRouter>
          <div>
            <Link className="App-link" to="/">Home</Link>
            &nbsp;|&nbsp;
            <Link className="App-link" to="/page2">Page2</Link>
          </div>
          <Switch>
            <Route exact path="/">
                <img src={logo} className="App-logo" alt="logo" />
                <p>
                  Edit <code>src/App.js</code> and save to reload.
                </p>
                <a
                  className="App-link"
                  href="https://reactjs.org"
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  Learn React
                </a>
                <p>The current time is {currentTime}.</p>
            </Route>
            <Route path="/page2">
                <p>This is page 2!</p>
            </Route>
          </Switch>
        </BrowserRouter>
      </header>
    </div>
  );
}

export default App;

With these changes, the application adds a small navigation bar that allows you to switch between the two pages:

React-Router demo

Deployment Changes to Support Client-Side Routes

If you play with the updated application using the development web server you will notice that the URL in the navigation bar of the browser is updated as you move through pages. This is actually quite nice, as it also includes a functional back button.

Because the URL is updated as links are clicked to navigate through client-side routes, it is quite possible that at some point the user may decide to refresh the page while the URL is not the main page. If you try this while using the development server it is not a problem, but on a production deployment the refresh is going to fail with a 404 error, because the server, be it nginx or gunicorn, will receive a request for a URL such as https://example.com/page2 instead of the expected https://example.com/, and the /page2 path is not something the server knows about.

To try this, build and deploy your project using one of the methods I described in the previous part of this series. Then navigate to the second page and hit refresh.

The following sections explain how to reconfigure the server in the two deployment options I discussed in the previous part of the series to support client-side routes.

Nginx

The React application is served by the Nginx server through the following configuration block:

    location / {
        try_files $uri $uri/ =404;
        add_header Cache-Control "no-cache";
    }

You can see that in the try_files clause Nginx tries the requested path as a file first, and then as a directory. If both attempts fail, then a 404 response is configured.

The trick to make routes work is to change that final 404 error into returning the index.html page, which bootstraps the React application:

    location / {
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "no-cache";
    }

Make the above change and reload the Nginx configuration with:

$ sudo systemctl reload nginx

And now the /page2 URL (or actually any other URL, even invalid ones) will cause the React application to render. Once the application bootstraps, React-Router will look at the actual URL and apply the routing, so not only this prevents the 404 error, but also shows the correct page.

Python Web Server

If you are using a Python web server such as Gunicorn, a similar change needs to be made, so that any unknown URLs that are requested return the application's index.html.

This can be done directly in the Flask application by adding a 404 error handler. For the example project I have been building, you can add this in the api.py file:

@app.errorhandler(404)
def not_found(e):
    return app.send_static_file('index.html')

This effectively "flips" the error condition and changes it to a success request that returns the page that bootstraps the React application.

Conclusion

I have received many questions regarding client-side routing, so I hope this article addresses them. If you have any problems that I have not addressed, please let me know below in the comments!

Leave a Comment