Serving Frontend Assets in Flask

When Python developers manage presentation layers.

If you're familiar with Django (or Python to any extent), you've probably accepted the concept of what Python guys refer to as "static assets." Let's take a moment to consider this: at some point in Django's conception a design decision was made: all JavaScript, CSS, or any assets pertaining to the presentation layer belong tucked away into an isolated corner of the framework. Alas, it was decreed: henceforward, only two types of code exist in the universe: Python, and not-Python. Anything that isn't Python is simply a "static" asset to be dealt with either locally or via a CDN somewhere.

The implied mindset is both somewhat absurd yet also unsurprising. It's easy to imagine a couple of backend nerds nonchalantly writing off frontend development as a concept to be stuck in a single folder. You know, all those things which make an app an app? Like, anything visible to a user? These are simply things-to-be-dealt-with when the frontend guys are ready with their "make it pretty" nonsense. Screw it, throw it all in the /static folder.

We accept this distinction to this day with little thought. Flask has carried over the same terminology and concept of a "static" folder, so this amusing cultural relic is here to stay.

Application Structure For Flask Apps with Frontend

Best practices aside, we'll start with the most straightforward project structure for a Flask web app. Let's work with this structure:

myproject
├─ /static
│  └─ /js
│  └─ /less
│  └─ /img
│  └─ /build
├─ /templates
└─ app.py

All the Python we need is going to sit in a plump little app.py file.

Templates hold the same concept as they do to equivalent frameworks such as Express: this is where we contain pages, partials, and layouts. Flask's default Jinja2 templating engine has personally served me well enough to never investigate an alternative.

/Static is where we'll store the raw source files for things like preprocessed stylesheets and JavaScript. At runtime, these sources files will be compressed and stored elsewhere in a production setting, such as that nifty /build folder we created (or feel free to call it /dist, whatever, it's your party man).

It's best practice to serve these from a CDN at runtime, but whatever.

In contrast to NodeJS apps, things like images and fonts are stored in the /static folder as well. This distinguishes Python's concept of /static from, say, another framework's concept of /src: instead of being a folder of only source code, we pretty much have a folder of all that shit which isn't Python. And we've come full circle.

The Toolbox

There's more than one way to skin a cat, and there's certainly more than one library or philosophy for serving pages and styles in Flask. flask_static_compress is one such library, which we'll include in app.py:

from flask import Flask, url_for, render_template, request, Response
from flask_static_compress import FlaskStaticCompress

flask_static_compress is a cool library for compressing and joining assets together. If you've ever used Gulp, it achieves some of the common tasks Gulp might, but with a MUCH different philosophy. Some key differences are:

  • The ability to served compressed assets individually, as opposed to one giant site bundle.
  • Never needing to explicitly fun a build command to create files served at runtime.

Creating The Flask App Object

from flask import Flask, url_for, render_template, request, Response
from flask_static_compress import FlaskStaticCompress

app = Flask(__name__, static_folder="static", template_folder="templates")

We initiate our app on line 4 with app = Flask().  We create our app with three parameters/attributes:

  • __name__: Now that I think about it, I've never actually considered what this first parameter is doing. Just know that you need it,
  • static_folder: Specifies the name of the folder where static assets will be served. This is based on where your app believes the root folder lives. Because our app is a single directory, it knows that /static is in the current directory.
  • template_folder: Same as above, but contains Jinja2 templates, AKA the files which become HTML.

It's important to reiterate that these folders default to being relative to the current location of app.py. If we want to change this behavior, we can pass instance_path=/path/to/desired/dir/ to override this behavior.

Serving our Lame Site

Before serving any assets, we'll need a landing page for our app. While still in app.py, we need to set a route for anybody who visits our site:

from flask import Flask, url_for, render_template, request, Response
from flask_static_compress import FlaskStaticCompress

app = Flask(__name__, static_folder="static", template_folder="templates")

@app.route('/', methods=['GET'])
def home():
    """Landing page."""
    return render_template('/index.html', title="Lame Site")

Our route listens for traffic hitting "/" (our root directory) and kindly serves them the page index.html as you might expect. Because we set the value of templates_folder just now, Flask knows to serve a file living in /templates/index.html.

If you need some pointers on creating basic templates in Flask in which to load your frontend assets, I'd suggest taking a look back at the previous post.

Frontend YOLO Swag

Back to your project, make .less file and a .js file in the appropriate places:

myproject
├─ /build
├─ /static
│  └─ js
│  │  └─ main.js
│  └─ less
│  │  └─ style.js
│  └─ img
├─ /templates
│  └─ layout.html
│  └─ index.html
└─ app.py

Use these files to brand your site accordingly. Add some images, memes, propaganda, viruses, etc. Whatever your heart desires.

Back in app.py we need to finish configuring our library:

from flask import Flask, url_for, render_template, request, Response
from flask_static_compress import FlaskStaticCompress

app = Flask(__name__)
app.config['COMPRESSOR_DEBUG'] = app.config.get('DEBUG')
app.config['COMPRESSOR_STATIC_PREFIX'] = 'static'
app.config['COMPRESSOR_OUTPUT_DIR'] = 'build'
app.static_folder = 'static'
compress = FlaskStaticCompress(app)


@app.route('/', methods=['GET'])
def home():
    """Landing page."""
    return render_template('/index.html', title="Lame Site")

Congrats! the main part of your application is pretty much done, just note a few things:

  • Notice this time we set our folder paths via app.config[] as opposed to inline, in the earlier example. This is simply a matter of preference.
  • compress = FlaskStaticCompress(app) initializes our library, so definitely do that.
  • Fun tidbit: app.static_folder = 'static' is a snippet which can live within any route to override the default app settings for where your folders are located.

Compressed 2 deff

Let's wrap this bad boy up. Back in layout.html, let's add those static files we created.

<!doctype html>
<html>
<head>
  <title>{% block title %}{% endblock %} - My Lame Site</title>
  <!-- Meta Data -->
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    
  <!-- CSS -->
  {% compress 'css' %}
     <link href="{{ url_for('static', filename='less/home.less') }}" type="text/less">
  {% endcompress %}
    
  <!-- JS -->
    {% compress 'js' %}
      <script src="{{ url_for('static', filename='js/previews.js') }}"></script>
	{% endcompress %}
</head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

Let's analyze this real quick:

{% compress 'css' %}
   <link href="{{ url_for('static', filename='less/home.less') }}" type="text/less">
{% endcompress %}

All files within the {% compress 'css' %} block will be minified and joined into a single file, and then moved to the build folder we specified earlier. It's that easy- no jobs to run beforehand, etc.

What might also catch your eye is how we define the path:

{{ url_for('static', filename='less/home.less') }}

This is a Jinja path dynamically locating our source file from our specified static doc. Well, I'll be darned.

Final Thoughts

I like FlaskStaticCompress because it feels, well, Pythonic. I know exactly what is happening to each file simply by looking at my page template code, without building complex Gulpfiles.

FlaskStaticCompress is great, but Flask_assets might even be better. I should've led with that from the get-go, but then again, now I can write an article specifically about Flask_assets and get double the hits! Whatever- nobody actually reads the last paragraph of any post anyway. By the way, I hate you. You suck. Bite me.

The great thing about Flask (and Python in general) is that you're provided the luxury to write code in a manner which you find enjoyable. Considering Flask is a relatively young framework, we're in a bit of a golden age where there are just enough libraries to suit your tastes, but not enough to get lost in an NPM level hell.

In my completely biased and untrustworthy opinion, it's hard to imagine getting involved with a Framework in a sweeter spot than where Flask is right now.

Todd Birchard's' avatar
New York City Website
Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.