42

I'm building the Flask app with React, I ended up having a problem with routing.

The backend is responsible to be an API, hence some routes look like:

@app.route('/api/v1/do-something/', methods=["GET"])
def do_something():
    return something()

and the main route which leads to the React:

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

I'm using react-router in the React app, everything works fine, react-router takes me to /something and I get the rendered view, but when I refresh the page on /something then Flask app takes care of this call and I get Not Found error.

What is the best solution? I was thinking about redirecting all calls which are not calling /api/v1/... to / it's not ideal as I will get back the home page of my app, not rendered React view.

laser
  • 1,388
  • 13
  • 14
knowbody
  • 8,106
  • 6
  • 45
  • 70
  • 2
    The fastest and easiest way if you do not want your backend to handle the urls is not to use the HTML5 history api. Deactivating it will lead to hashbang urls like `/#/something` and reloading them will always trigger the `/` view on the backend. Otherwise you need to either handle the requests on your backend or redirect them to your react app, which in turn needs to analyze the request and trigger the location change if the requested url is not `/`. – sthzg Jun 03 '15 at 12:45
  • That seems like some kind of solution, thanks! – knowbody Jun 03 '15 at 12:50
  • 4
    I don't think it's a good solution. It's 2015, you really should use History API. – Dan Abramov Jun 03 '15 at 12:53
  • @DanAbramov exactly, but how? – knowbody Jun 03 '15 at 12:54
  • There could be one more way - you can simply write a re-write statement in .htaccess file so every-time that particular `#something` comes through react-router instead flask. This could be an efficient solution in case URL are based on certain pattern in case not then its waste of time. – Pralhad Narsinh Sonar Jun 03 '15 at 13:04

5 Answers5

39

We used catch-all URLs for this.

from flask import Flask
app = Flask(__name__)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return 'You want path: %s' % path

if __name__ == '__main__':
    app.run()

You can also go an extra mile and reuse the Flask routing system to match path to the same routes as client so you can embed the data client will need as JSON inside the HTML response.

Alireza
  • 100,211
  • 27
  • 269
  • 172
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • 1
    if all the routes will be directed to catch all function, how would you separate backend api routes and react routes when i do a ajax request call from front end? – shangsunset Dec 22 '15 at 16:29
  • 7
    @shangyeshen Register API routes before catch-all so they have precedence. – Dan Abramov Dec 22 '15 at 16:59
  • Is there a complete example of this somewhere? The code here doesn't do any index rendering like the example above and I have been unable to combine the two in a way that works. – juanitogan Mar 05 '16 at 04:57
  • 5
    Found it. I couldn't get `@app.route('/')` to match on anything until I figured out the poorly documented Flask parameters. Setting `static_url_path=""` is bad. I ended up with `static_url_path="/public", static_folder="../public"`. Then I return a static: `return app.send_static_file("index.html")` with hard-coded `"/public/..."` urls. Also, look at: https://www.reddit.com/r/reactjs/comments/42pn95/reactrouter_and_flask_404/ (not me). – juanitogan Mar 05 '16 at 21:52
  • This is the solution I used for a while until I needed to break my bundle into parts using `React.lazy`. `React.lazy` wants to get the bundle parts from the `path` instead of the static directory that flask is using. Any ideas on that? – Jason Aug 23 '19 at 10:40
  • Hello @Jason, what is the issues your are getting in React.lazy. We are trying to go down this route and I am researching all pros and cons this approach. Thank you. – logeekal May 18 '20 at 13:42
6

Maybe as extension to the answers before. This solved the problem for me:

from flask import send_from_directory

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def serve(path):
     path_dir = os.path.abspath("../build") #path react build
     if path != "" and os.path.exists(os.path.join(path_dir, path)):
         return send_from_directory(os.path.join(path_dir), path)
     else:
         return send_from_directory(os.path.join(path_dir),'index.html')
Henrik
  • 521
  • 3
  • 7
  • 16
4

For some reason, the catch-all URLs did not work for me. I found that using the flask 404 handler results in the exact same thing. It sees the url and passes it down to react where your router will handle it.

@app.errorhandler(404)   
def not_found(e):   
  return app.send_static_file('index.html')
João Ramiro
  • 312
  • 1
  • 9
1

Just to inform handle error 404 and render_template works perfectly for me.

@app.errorhandler(404)
def not_found(e):
    return render_template("index.html")
Kam Dane
  • 50
  • 1
  • 9
0

I have to combine both catch-all and 404 handler for it to work properly. I am hosting a react-app in a subpath with its own redirection handler from react-router.

@app.route('/sub-path',  defaults={'path': 'index.html'})
@app.route('/sub-path/<path:path>')
def index(path):
    return send_from_directory('../react-dir/build', path)

@app.errorhandler(404)
def not_found(e):
  return send_from_directory('../react-dir/build','index.html')