Static Files Made Easy — Django Compressor + Whitenoise + AWS CloudFront + Heroku

Zilong Li
TechnoLingo
Published in
6 min readOct 23, 2018

Deploying your Django web app can be as easy as printing ‘Hello World!’ or as hard as launching a rocket. Different configurations, however, result in significantly different levels of performance. Whilst there are many aspects in web deployment, this article focuses on the best* way to serve static files for your Django app with contemporary cloud infrastructure.

*Best meaning a good balance between performance and simplicity. Of course, this is subjective. I prefer Whitenoise over Nginx since it is in Python and is much easier to set up and maintain. In some cases, Nginx might be preferred.

Tutorials galore on how to set up Django with AWS S3 or Whitenoise, but rarely can one find detailed guides on configuring Whitenoise, AWS CloudFront, AND Django Compressor. Some even say it is impossible. After many hours of tinkering, I find it to be my solemn duty to share this obscure bit of knowledge with the world and posterity.

Is Django Compressor Necessary, Though?

Maybe. It really depends on your use case. Whitenoise already minimises and compresses static files, but Django Compressor also combines multiple static files into one single file, thus reducing the number of requests the client has to make to your server. This can be a minor improvement on load speed and server efficiency. If you are deploying a personal project, then the performance boost is probably not worth the added complexity. For a large-scale production app, however, these kinds of minor improvements add up and can lower your bounce rate.

Isn’t Django Compressor Incompatible with Whitenoise?

Normally, yes. But you could use Django Compressor’s offline compression mode to make it compatible with Whitenoise.

Getting Started

Assuming you have a working Django project ready to deploy, the first step is to install and configure Whitenoise and Django Compressor:

pip install whitenoise[brotli]
pip install django_compressor

Add Django Compressor to your INSTALLED_APPS in your settings.py (or the equivalent config file according to your setup):

INSTALLED_APPS = (
# other apps
"compressor",
)

Modify your requirements.txt as below (change the version numbers to the ones you installed, obviously):

# static files
whitenoise==3.3.1
brotlipy==0.7.0
django-compressor==2.2

Add Whitenoise middleware as below:

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # place it here
# other middleware
]

Following the 12-Factor App methodology, we use environment variables to configure our static file server as below (also install django-environ if you haven’t already):

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_HOST = env('DJANGO_STATIC_HOST', default='')
STATIC_URL = STATIC_HOST + '/static/'
# ! Set this to where you put your static files (js, css, images, fonts.)
STATICFILES_DIRS = [
str(APPS_DIR.path('static')),
]
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder' # Django-Compressor
]
# whitenoise
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

Add the Django Compressor settings as below. As the time of writing, Django Compressor does not support Brotli compression just yet, despite of how its documentation claims otherwise. Also, as I said earlier, you must enable the offline compression mode in order to make Django Compressor compatible with Whitenoise.

COMPRESS_STORAGE = 'compressor.storage.GzipCompressorFileStorage'COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', default=True)
# Must enable this to use with Whitenoise
COMPRESS_OFFLINE = env.bool('COMPRESS_OFFLINE', default=True)

In your templates, you can call the compress tag to combine multiple static files of the same type (either all CSS or JavaScript) into a single file as below (You also cannot have other logic such as an ‘if’ condition inside the compress tag):

{% load static compress %}{% compress css %}
<!-- Font-Awesome 4 Icons -->
<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" />
<!-- Project-Level Master CSS -->
<link rel="stylesheet" href="{% static 'css/project.css' %}" />
{% endcompress %}
{% compress js %}
<!-- jQuery-3.2.1 -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<!-- Project-Wide Custom JS -->
<script src="{% static 'js/project.js' %}"></script>
{% endcompress %}

Deploying It to the Cloud

The next step is to push the project to the cloud. Victory is ahead! No matter what PaaS or VPS provider you are using, the basic principles are the same; and you should be able to follow along. You will, however, need to be able to set environment variables. For this demonstration, I use Heroku as the cloud provider.

Since we are using Django Compressor’s offline compression mode, we must run the compress command before each deployment. By adding a file named ‘bin/post_compile’ in the project root directory, Heroku will automatically execute the commands for us.

First, disable Heroku’s automatic static file collection feature by running the following command in your terminal inside your project repository (Alternatively, you could set these environment variables on the Heroku website by going to your project settings then clicking ‘reveal config vars’.):

heroku config:set DISABLE_COLLECTSTATIC=1

Then, create a folder named ‘bin’ under your project root directory. Create a file named ‘post_compile’ inside the said folder with no file extension. Copy and past the following to that file and save:

python manage.py collectstatic --noinput
python manage.py compress --force
python manage.py collectstatic --noinput

You can now push your code to Heroku as you normally would. The above script will run the offline compression command and collect your static files for you every time you deploy your code to Heroku. If you are using another cloud provider, make sure you find a way to set environment variables and run the above ‘compress’ and ‘collectstatic’ commands during each deployment.

After the deployment is done, you can search the keyword ‘compressing’ in your terminal and see if offline compression is working correctly. If so, you should be able to see something like this:

Setting Environment Variables

As you have noticed, in the above settings, there are a few environment variables that need to be set on your server. We do this instead of hard-coding those values to make our setup more configurable, secure, and maintainable. Either through Heroku’s (or your cloud provider’s) website or command line interface, we need to set both COMPRESS_ENABLED and COMPRESS_OFFLINE to True. We can leave DJANGO_STATIC_HOST for now since we haven’t created our Content Delivery Network (CDN) distribution, yet.

Adding a Content Delivery Network (CDN)

To take this speed optimisation theme to the next level, we will use a Content Delivery Network (CDN) to make our website load fast anywhere in the world. In this example, we use Amazon Cloud Services (AWS) CloudFront as the CDN provider. You can also use another CDN provider such as Cloudflare in your setup. The basic steps should be the same.

Why a CDN? Isn’t that Excessive?

Well, if adding a CDN is not on your agenda, then I would recommend using AWS S3 + Django-Storages instead of Whitenoise and Django-Compressor. One of the major benefits of using Whitenoise is the ability to integrate with a CDN. Without this benefit, the much simpler approach with AWS S3 serving both static and media files would make more sense.

Moreover, in my opinion, pricing of CDN services is becoming more reasonable. Take AWS CloudFront for example, it is included in the Free Tier; and you pay for your usage thereafter. In other words, if you don’t have much traffic, you won’t pay much. If you do have a lot of traffic, then your priority should be reducing bounce rate, not saving money on infrastructure.

Configuring AWS CloudFront

Log into AWS Console and go to the CloudFront panel. Click ‘Create Distribution’ and select ‘Web’. Use your domain name (without http or https) as the ‘Origin Domain Name’ and leave everything else as default. Once your distribution is ready, copy the distribution domain name and set this value to the DJANGO_STATIC_HOST environment variable on your server.

For Heroku, it will be something like this:

heroku config:set DJANGO_STATIC_HOST=https://d4663kmspf1sqa.cloudfront.net

Finishing Up

Open your project website and see if everything works correctly. A wise man once said: “Great things are rarely accomplished at the first attempt.” You might need to fix a few minor issues related to your particular setup.

Once the website is loading correctly, use an online speed tester (such as https://www.webpagetest.org/) to check if the static files are compressed and cached as they should.

In order to properly cache the static files generated by Django Compressor, we must also override Whitenoise’s default max age setting in your settings.py (but don’t do this before you know everything is working correctly):

WHITENOISE_MAX_AGE = 31536000 if not DEBUG else 0

Congratulations! Your hard work has paid off. Now your Django app is taking full advantage of your advanced setup and delivering static files as fast as possible to users anywhere in the world. Hooray!! Thanks for reading. Subscribe to this blog for similar tutorials in the future. :)

--

--