Hot reloading with React and Flask
Introduction
A friend of mine recently set up a new project. Having positive experiences with both in the past, he chose Flask and React as the backbone of his stack. By serving the React build output as Flask static files, he was able to quickly get a production-ready application off the ground.
As the application grew though, compilation of the React App began to take more time, slowing his development progress. He reached out to me asking if I knew a good solution to restore hot reloading and speed up the feedback cycle for making React changes. This article shares the result we came up with.
If you would rather to skip to the code, I have published the resulting boilerplate project here: https://github.com/ajhyndman/flask-react
If you feel comfortable setting up a React and Flask application on your own, you may want to skip to: Support hot-reloading in development
Prerequisites
To run Flask and React, you’ll need Python, Node and a package manager for each language installed on your computer. For this article, I use (and recommend) the following:
- Python (https://www.python.org/downloads/)
- Poetry (https://python-poetry.org/docs/#installation)
- Node (https://nodejs.org/en/download/)
- yarn (https://classic.yarnpkg.com/en/docs/install)
Bootstrap your project
Create a new folder to hold your project.
I recommend going ahead and initialising a version control repository right away. As I write this, git is the most popular by far:
~/Projects/my-app $ git init
Flask
Using Poetry, you can create a new folder, Python virtualenv and project definition file with a single command.
~/Projects/my-app $ poetry new server
This should create the following folder structure for you.
server
├── pyproject.toml
├── README.rst
├── server
│ └── __init__.py
└── tests
├── __init__.py
└── test_server.py
Next, install Flask.
~/Projects/my-app/server $ poetry add flask
Define your Flask application.
# my-app/server/server/app.py
from flask import Flaskapp = Flask(__name__)@app.route("/")
def getRoot():
return "Welcome!"
I recommend using a .flaskenv
file to configure persistent environment variables, so that you don't need to remember to type them every time.
~/Projects/my-app/server $ poetry add python-dotenv# my-app/server/.flaskenv
FLASK_APP=server.app
Now you can start your Flask application with the flask CLI.
~/Projects/my-app/server $ poetry run flask run
In a web browser, open http://localhost:5000/, and you should see your brand new Flask-powered website.
React
Use create-react-app to initialise a React application in the my-app/client
folder:
~/Projects/my-app $ yarn create react-app client
You should immediately have a ready-to-launch React application.
~/Projects/my-app/client $ yarn start
Serve your React application from Flask
There are many solutions for serving static assets alongside a Flask application, but until your project needs more I think it’s a good idea to keep things simple.
Flask has out of the box support for serving static files from the /static folder, relative to app.py
.
Update your React app’s build script to publish static files to this folder.
// my-app/client/package.json
{
// ...
"scripts": {
"build": "BUILD_PATH='../server/server/static' react-scripts build",
}
}
Now mount your React application at a new Flask route.
# my-app/server/server/app.py# ...@app.route("/app/", defaults={"path": "index.html"})
@app.route("/app/<path:path>")
def getApp(path):
return app.send_static_file(path)
Lastly, tell your React application about the public path you mounted it at.
// package.json
{
// ...
"homepage": "/app/"
}
Build and launch your application.
~/Projects/my-app/client $ yarn build~/Projects/my-app/server $ poetry run flask run
Now you should see your react application mounted at http://localhost:5000/app/
Support hot-reloading in development
You’ve done it! Now you have a functional React application mounted at a Flask route. Everything you love about React and Flask are ready to go at your fingertips. But that’s not why you came here. Right now, every time you update any React code, you need to rebuild the application:
~/Projects/my-app/client $ yarn build
And every time you update any python files, you need to restart your flask server:
~/Projects/my-app/server $ poetry run flask run
You’ll never get anything done like that! Fortunately, with a little more configuration, we don’t have to compromise anything we’ve done to this point to get a hot-reloading workflow in development.
Enable Flask development mode
By default, flask run
launches a production-friendly server process. However, you can opt into a hot-reloading debug mode by setting the FLASK_ENV environment variable.
# my-app/server/.flaskenv
FLASK_APP=server.app
FLASK_ENV=development
Now, changing any python source files will automatically restart the flask process.
Serve React Application from Webpack Dev Server
Create React App’s supported hot-reloading workflow is the yarn start
script. This starts Webpack Dev Server, which serves static assets from localhost:3000. In Flask, during development, we can pass static asset requests on to Webpack Dev Server, using a simple reverse proxy implementation.
~/Projects/my-app/server $ poetry add requests# my-app/server/server/app.py
from os import environfrom flask import Flask, request
from requests import getIS_DEV = environ["FLASK_ENV"] == "development"
WEBPACK_DEV_SERVER_HOST = "http://localhost:3000"app = Flask(__name__)
def proxy(host, path):
response = get(f"{host}{path}")
excluded_headers = [
"content-encoding",
"content-length",
"transfer-encoding",
"connection",
]
headers = {
name: value
for name, value in response.raw.headers.items()
if name.lower() not in excluded_headers
}
return (response.content, response.status_code, headers)
@app.route("/app/", defaults={"path": "index.html"})
@app.route("/app/<path:path>")
def getApp(path):
if IS_DEV:
return proxy(WEBPACK_DEV_SERVER_HOST, request.path)
return app.send_static_file(path)
Since you’ll be serving your app via Flask, you can disable Webpack Dev Server’s default behaviour of launching a new browser tab.
# my-app/client/.env
BROWSER=none
Now if you start Webpack Dev Server alongside Flask in development mode, you should see your application restored at http://localhost:5000/app/.
~/Projects/my-app/client $ yarn start
~/Projects/my-app/server $ poetry run flask run
Bypass Flask with Webpack Dev Server’s socket connection.
You’ll notice that at this point one last issue remains. Flask is automatically restarting when you change a Python file. Webpack recompiles when you change a JavaScript file. But you still have to manually refresh your page to see those changes.
We can do better.
Webpack Dev Server opens a web socket connection with the browser which allows it to ping the browser when something has changed. Flask doesn’t support web socket connections, but that’s okay, because we can easily bypass Flask for this connection. We can tell Webpack to look for this connection on port 3000 instead of using the default window.location.host
.
# my-app/client/.env
BROWSER=none
WDS_SOCKET_PORT=3000
Conclusion
And that’s it!
Now, with FLASK_ENV=production you have a deployable flask application serving statically built React assets, and with FLASK_ENV=development you have a modern, hot-reloading workspace that incrementally rebuilds changes to both your React and Flask applications.