Frontend in Flask: Page Templates and Assets

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" without much consideration. At some point in Django's conception, a design decision was made: any code, styles, or assets were tucked away into their own respective uninteresting corners of the framework. Thus it was known: anything that isn't Python is simply a "static" asset to be dealt with either locally or via a CDN somewhere. Basically, we as Python nonchalantly accept that all the things which make an app an app, such as anything visible to a user, is simply something to be dealt with. Ya know, like, over there or something. Screw it, just make a folder for all of it. Or two.

While something as simple as terminology reveals a lot about how we think about our codebase, apparently nobody thinks this is amusing except me. Fine, whatever. Let's just get to making an app that has some pages and styles and stuff.

Basic Application Structure

We're going to keep this simple... perhaps too simple, but that's the beauty of Flask: We can create apps with just a few files and folders, and it's totally acceptable. Let's work with this structure:

myproject
├─ /build
├─ /static
│  └─ js
│  └─ less
│  └─ img
├─ /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 Jinja templating engine has personally served me well enough to never investigate an alternative.

Static is where we we'll store the source files for things like stylesheets and Javascript, which will be compressed and stored elsewhere in a production setting, such as that nifty build folder we created. It's best practice to serve these from a CDN at runtime, but whatever for now. Interestingly, things like images and fonts are typically stored in the static folder as well, this distinguishing Python's concept of /static from say another framework's concept of /src: instead of being a folder of source code, we pretty much have a folder of all that shit which isn't Python.

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. I'm picking the ones I like here 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 - one that I personally think is more enjoyable to use, despite potential downsides.

Before messing with any assets, we'll need a page for our app.

Filthy Frontend

Let's add layout.html and index.html files to our templates folder. Note that these are in fact HTML file extensions, but they are oh-so-much more.

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

The idea here is there are certain HTML elements we want on every page of our app (metadata, analytics, etc). Layout.html is going to be the 'page we load other pages into' so we only need to handle that once:

<!doctype html>
<html>
<head>
  <title>{% block title %}{% endblock %} - My Lame Site</title>
  <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">
  <!-- etc etc, analytics and whatnot -->
</head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

Stating {% block content %}{% endblock %} is reserving the area of our layout in which templates such as our homepage will be loaded into. Speaking of our homepage:

<!-- index.html -->
{% extends 'layout.html' %}

{% block content %}
<div class="container>
<h1>My Lame site</h1>
    <p>Hello, and welcome to my lame site! I'm so glad you're here. I'm so lonely.</p>
</div>
{% endblock %}

{% extends 'base.html' %} immediately states that this page is an extension of layout.html, thus will only ever live in the context of the layout we created earlier. After this is declared, we can fill blocks we reserved previously in layout.html such as {% block content %}. In theory, we can reserve as many "blocks" in our layout as we want and thus respectively serve multiple blocks within the same template.

Serving our Lame Site

Back in app.py we can now set a route for anybody who hits our homepage:

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

We initiate our app on line 4, where we also declare our static and template folders will be aptly named static and template. It's important to note here that this is relative to the current location of app.py. If we want to change this behavior, declaring /static and /template will refer to the project root, as opposed to a relative path.

Our route listens for traffic hitting "/", our root directory, and kindly serves them the page index.html as you might expect. Via Flask's render_template method we can also pass values into our template where they are reserved, such as with the page title.

At this point we technically have a working app, but let's spice it up with some flavorful CSS and JS.

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 memes, spread propaganda, or 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 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 personally 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.

Without a doubt, this library serves one purpose only whereas Gulp does things I don't think any of us even need and beyond. They're hardly comparable... I just happen to find this method more enjoyable than when I'm in Express.

There are other ways to handle these uses cases via different libraries. Flask_assets is a perfectly legitimate alternative. 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.

Author image
New York City Website
Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.

Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.