Search code examples
herokuflaskstatic-files

How to point Flask static to Amazon S3 URLs?


I have a Flask app hosted on Heroku but want the static files to be served from Amazon S3.

In my templates I use url_for() for all references to static files. In the initialization of the Flask app I then want to put

app = Flask(__name__, static_url_path="http://my-bucket.s3.amazonaws.com")

to make sure instead of mysite.com/static/, http://my-bucket.s3.amazonaws.com/static/ is used. However I get this error:

ValueError: urls must start with a leading slash

If I change it to a value with a leading slash it works, but I want the static URL to point to S3, an external domain so it needs to start with http://.

What am I doing wrong? How can I use S3 for static files with Flask and Heroku?


Solution

  • If you are using any of the static_* options to the Flask object, it is assuming that it'll be responsible for serving those files. A static route is configured that serves both as the view to serve the static files and as the url_for() target to generate the URLs.

    So, with Flask alone, you'd have to replace all url_for('static', ...) calls with hardcoded URLs to your CDN instead.

    Instead, you should switch to using Flask-CDN, a handy Flask add-on to handle seamless switching between static files hosted by Flask and by a CDN:

    from flask_cdn import CDN
    
    app = Flask(__name__)
    cdn = CDN(app)
    

    and set the CDN_DOMAIN configuration option to http://my-bucket.s3.amazonaws.com when deploying to production.

    In debug mode, url_for() will then generate the old /static/.. urls for Flask to handle, in production mode, url_for() prefixes those urls with the CDN_DOMAIN value. If the latter is left to the default None setting, no such URL alterations take place, making it possible to run Flask locally with debug switched off as needed.

    Note that the behaviour of url_for() is only altered for Jinja templates; if you need to generate static URLs in your views, you'll have to swap flask.url_for() out for flask_cdn.url_for().