使用Flask来为create-react-app创建的前端提供服务

96

我有一个使用Flask编写的后端,其中包含API路由,可以通过使用create-react-app创建的React单页应用程序进行访问。当使用create-react-app开发服务器时,我的Flask后端可以正常运行。

我希望从我的Flask服务器中提供已构建(使用npm run build)的静态React应用程序。构建React应用会导致以下目录结构:

- 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]

所谓[crypto],指的是在构建时随机生成的字符串。

浏览器在接收到index.html文件后,会发起以下请求:

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

我应该如何提供这些文件? 我想到了这个:

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')

这样,静态资源就可以成功访问了。

另一方面,我可以将其与内置的Flask静态工具结合使用。但是我不知道如何配置它。

我的解决方案是否足够健壮?是否有一种方法可以使用Flask内置功能来提供这些资源?是否有更好的方法来使用create-react-app?


4
只要文件夹名为"static"并且与Flask入口文件放在同一级目录下,Flask就能自动识别静态文件夹。因此,在构建脚本中可以加入命令cp -rf /build/static ./static来复制静态文件夹。请注意,这样做无需其他操作,也不会改变原意。 - Joran Beasley
1
你也可以使用nginx来提供静态文件,这通常是推荐的方式(nginx对于静态文件非常出色)。 - patrick
5个回答

113
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)

这就是我最终得到的。基本上,捕获所有路径,测试路径是否为文件 => 发送文件 => 否则发送index.html。这样,您可以从任何您希望的路由重新加载react应用程序,而不会破坏它。


3
这个解决方案出现了MIME类型错误 :-( 该脚本具有不受支持的MIME类型('text/html')。/service-worker.js 无法加载资源:net::ERR_INSECURE_RESPONSE registerServiceWorker.js:71 服务工作者注册期间出错:DOMException: 无法注册服务工作者:该脚本具有不受支持的MIME类型('text/html')。 - ketysek
@user3216673,这很可能不是Flask或Create-react-app的问题,而是你的浏览器问题。猜一个可能的解决方法:注销你的服务工作者(Service Workers)。 - Jodo
1
你可以使用 app.static_folder 来保持你的代码DRY。 - Eytan
1
如果您想从子路径而不是根目录提供应用程序(就像我的情况一样),您可以执行以下操作:@app.route('/app', defaults={'path': ''}) @app.route('/app/')然后在您的 CRA 应用程序 package.json 中添加 homepage: "./app" - Y.V.Reetesh
虽然对发生的事情没有完全的概念,但工作正常。哈哈 - imbr
显示剩余2条评论

21
这里有一个可行的解决方案。 你是否曾想过为什么我们需要两个单独的文件夹来存储"static"和"templates"的文件。是为了分隔混乱的内容,对吧? 但在生产环境中,它会成为一个问题,因为它只有一个文件夹来存放"static"和"templates"类型的文件,并且所有依赖项都以这种方式链接。 如果你将"build"文件夹视为"static"和"templates",则它将被提供服务。 请使用类似于以下内容的东西。
from flask import Flask, render_template

app = Flask(__name__, static_url_path='',
                  static_folder='build',
                  template_folder='build')

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

你的 Flask 应用程序会正常运行。


2
这个答案救了我。其他的答案都有一些限制条件,不适用于我的设置。 - user362178
2
static_url_path='' 这个是让它对我起作用的。 - Nicholas Obert

19

按照您之前提到的,首先执行npm run build以构建静态生产文件。

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')

很遗憾,我认为你不能在开发热重载中使其工作。


4
接受的答案对我没有用。我已经使用了 HTML 标签。
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



0

我使用了一个 Flask 服务器,只有一个路由 /,它从 Create React App(CRA)的 build 文件夹中读取 index.html 文件。

from flask import Flask
app = Flask(__name__)
app.static_folder =  '../build'

@app.route('/')
def index():
    fref = open(r'../build/index.html')
    html_text = fref.read()
    fref.close()
    return html_text

app.run()

在这种设置方式下,我遇到了一个错误,静态文件由于路径不匹配而无法正确提供,所以我使用的解决方案是

  1. 在 CRA 的 package.json 中添加一个主页属性,并将其设置为“/static”

{ "name":"App-name", "version":"", "dependencies":{} "homepage":"/static",....[其他键]}

 Add **homepage** key parallel to the **dependencies** key in the package.json file

这个homepage属性将在CRA的构建过程中使用,并用于替代index.html中的%PUBLIC_URL%,并附加到其他静态资源的URL路径上(您可以在构建过程后查看index.html代码进行验证)。
构建完成后,运行flask服务器,我们可以看到第一次GET请求带有“/”,然后会提供index.html,接着从HTML文件中的其他静态资源请求“/static/static/js/[[filename]]”,一切都正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接