Removing unused CSS in a Django template-based project

When productionizing a web app there are many best practices you can do to improve the performance of the deployed app. One of those best practices is to purge all the unneeded CSS classes and elements for quicker download times, and potentially faster performance when the browser is parsing through the CSS file (this is especially important in bigger sites where a huge CSS file might make the site perform slow).

This is almost a trivial task if you are working with a modern client-side JS framework. But if you are developing a web app using Django's template language with some CSS library (such as Bulma or Bootstrap), and potentially using HTMx for more dynamic behavior, this is not an easily solved problem.

These CSS libraries are great and allow you to develop web apps fast and easily, with the downside that they provide a single huge CSS file. Some of them do offer more modular CSS files in case you want to make the effort. But even in that case, you will include more classes that you will actually use (for instance there might be a module for all the button classes - but you will most probably use only a single style of buttons).

When I searched online I couldn't find an "industry standard" solution to this problem. What I ended up doing was using the popular tool PurgeCSS along with a quick Python script to generate the appropriate command. What the PurgeCSS tool does is search for all your HTML files, gather all the CSS classes used, and then "purge" all the unused ones from the CSS file. You just need to declare all the HTML files you have.

The only issue is that in a server-side rendering Django project, the template files (which are HTML files along with some special tags) are scattered across many folders. Each app has its  templates folder and if you have subfolders within each template folder the total location of HTML files is quite big. Using the following script, you just need to declare the root folder of the project, the CSS files, and the output file of the "purged" CSS file to be created. Then run the command and PurgeCSS will do the rest!

import os

css_files = ["/path/to/project/static/style.css"]

html_files = []
for root, dirs, files in os.walk("/path/to/project/"):
    for file in files:
        if file.endswith('.html'):
            absolute_path = f"{root}/{file}"
            html_files.append(absolute_path)
output_file = "/path/to/project/static/web/style.min.css"

print(f"purgecss --css {' '.join(css_files)} --content {' '.join(html_files)} --output {output_file}")

Caveats

There are a few caveats to this approach. The PurgeCSS tool understands only regular HTML. If you are using any Django template tag to set CSS classes (such as django-widget-tweaks), then these classes won't be identified and will be removed. The same applies if you are setting dynamic CSS classes, such as using some JS script.

There are multiple potential solutions. PurgeCSS supports a --safelist parameter where you can provide all the classes that should not be removed. Alternatively, you can write an extractor to identify those classes.

Hopefully, this was useful for your needs and you can make your web app a bit faster!

Happy coding!