Serving a create-react-app with Flask

2020-02-02 04:47发布

问题:

I have a flask back-end with API routes which are accessed by a React single page application, created using the create-react-app boilerplate. When using the create-react-app built-in dev server, my Flask back end works, no problem with that.

Now, I would like to serve the built (using npm run build) static react app from my Flask server. Building the react app leads to the following directory structure:

- build
  - static
    - css
        - style.[crypto].css
        - style.[crypto].css.map
    - js
        - main.[crypto].js
        - main.[crypto].js.map
  - index.html
  - service-worker.js
  - [more meta files]

By [crypto], I mean the randomly generated strings generated at build time.

Having recieved the index.html file, the browser then makes the following requests:

- GET /static/css/main.[crypto].css
- GET /static/css/main.[crypto].css
- GET /service-worker.js

My question is then: how should I go about serving these files ? I came up with this:

from flask import Blueprint, send_from_directory

static = Blueprint('static', __name__)

@static.route('/')
def serve_static_index():
    return send_from_directory('../client/build/', 'index.html')

@static.route('/static/<path:path>') # serve whatever the client requested in the static folder
def serve_static(path):
    return send_from_directory('../client/build/static/', path)

@static.route('/service-worker.js')
def serve_worker():
    return send_from_directory('../client/build/', 'service-worker.js')

This way, the static assets are successfully served. But it is not a very elegant solution.

On the other hand, I could incorporate this with the built-in flask static utilities. But I do not understand how to configure this.

I really do not know how to handle this, to the point that it's making me reconsider my use of create-react-app, as it is forcing me to structure my static folder in a very specific an inconvenient way: There is no way for me to change how the app requests static content from the server.

Overall: Is my solution robust enough ? Is there a way to use built in flask features to serve these assets ? Is there a better way to use create-react-app ? Any input is appreciated. I can provide more information if needed.

Thanks for reading !

回答1:

import os
from flask import Flask, send_from_directory

app = Flask(__name__, static_folder='react_app/build')

# Serve React App
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def serve(path):
    if path != "" and os.path.exists(app.static_folder + '/' + path):
        return send_from_directory(app.static_folder, path)
    else:
        return send_from_directory(app.static_folder, 'index.html')


if __name__ == '__main__':
    app.run(use_reloader=True, port=5000, threaded=True)

Thats what I ended up with. So bascially catch all routes, test if the path is a file => send file => else send the index.html. That way you can reload the react app from any route you wish and it does not break.



回答2:

First do npm run build to build the static production files as mentioned by you above

from flask import Flask, render_template

app = Flask(__name__, static_folder="build/static", template_folder="build")

@app.route("/")
def hello():
    return render_template('index.html')

print('Starting Flask!')

app.debug=True
app.run(host='0.0.0.0')

Unfortunately, I don't think you can get it work with the development hot-reload.



回答3:

The accepted answer does not work for me. I have used

import os

from flask import Flask, send_from_directory, jsonify, render_template, request

from server.landing import landing as landing_bp
from server.api import api as api_bp

app = Flask(__name__, static_folder="../client/build")
app.register_blueprint(landing_bp, url_prefix="/landing")
app.register_blueprint(api_bp, url_prefix="/api/v1")


@app.route("/")
def serve():
    """serves React App"""
    return send_from_directory(app.static_folder, "index.html")


@app.route("/<path:path>")
def static_proxy(path):
    """static folder serve"""
    file_name = path.split("/")[-1]
    dir_name = os.path.join(app.static_folder, "/".join(path.split("/")[:-1]))
    return send_from_directory(dir_name, file_name)


@app.errorhandler(404)
def handle_404(e):
    if request.path.startswith("/api/"):
        return jsonify(message="Resource not found"), 404
    return send_from_directory(app.static_folder, "index.html")


@app.errorhandler(405)
def handle_405(e):
    if request.path.startswith("/api/"):
        return jsonify(message="Mehtod not allowed"), 405
    return e