Async CSS with Django Pipelines

Aug 27, 2019

The simplest way to load a CSS file in an HTML document is to use a link element with rel="stylesheet":

<link rel="stylesheet" href="mycssfile.css">

Referencing CSS this way works great, but it comes with a downside: it’s synchronous. In other words, with a typical stylesheet link like this, the browser stops rendering subsequent portions of the page while it requests, downloads, and parses the file.

Historically, there are several ways to make a browser load CSS asynchronously, though none are quite as simple as you might expect, like editing the document head for including the new stylesheets, or set the stylesheet link's media attribute to a media type (or query) that does not match the user’s current browsing environment and then toggle the media value to something that matches the user’s browsing environment in the onload event; thankfully, there’s now a web standard that is designed specifically for loading resources like CSS asynchronously: rel="preload"

How we use it with Django-Pipelines?

As you may probably know you can add arbitrary params in your PIPELINES_JS config, using the extra_content key:

PIPELINE = {
    'JAVASCRIPT': {
        'stats': {
            'source_filenames': (
                'js/jquery.js',
                'js/collections/*.js',
                'js/application.js',
            ),
            # Attributes go in this dict
            'extra_context': {'async': True},
            'output_filename': 'js/stats.js',
        }
    }
}

https://github.com/jazzband/django-pipeline/issues/307

Adding this keyword will make your JavaScript load async sadly this doesn't apply to the CSS config, for that reason we need to change the template that Django-Pipelines uses for render the content, inside your main templates folder create a new file named css.html like this <main_app>/templates/pipeline/css.html and add the following content:

{% if async %}
    <link rel="preload" href="{{ url }}" as="style" onload="this.onload=null;this.rel='stylesheet'" />
    <noscript><link rel="stylesheet" href="{{ url }}"></noscript>
{% else %}
    <link rel="stylesheet" href="{{ url }}" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if title %} title="{{ title|default:"all" }}"{% endif %}{% if charset %} charset="{{charset }}"{% endif %} />
{% endif %}

Now we need to change our PIPELINES config for the stylesheets:

PIPELINE = {
    'STYLESHEETS': {
        'theme': {
            'source_filenames': (
                'css/theme/bootstrap.css',
                'css/theme/icons.css',
                'css/friendly.css',
                'css/theme/app.css',
            ),
            'output_filename': 'css/theme.css',
        },
        'autocomplete_light': {
            'source_filenames': (
                'autocomplete_light/style.css',
            ),
            'extra_context': {'async': True},
            'output_filename': 'css/autocomplete.css',
        },
    }
}

And that's all, our theme.css will load synchronous and the autocomplete.css asynchronous preventing block the browser rendering and making your website load a littel bit faster, if you want to read more about async in CSS check the loadCSS project from filamentgroup that have a good explanation about all the edge cases.