tl;dr; I wrote my own extension to django-pipeline
that uses Zopfli to create .gz
files from static assets collected in Django. Here's the code.
Nginx and Gzip
What I wanted was to continue to use django-pipeline
which does a great job of reading a settings.BUNDLES
setting and generating things like /static/js/myapp.min.a206ec6bd8c7.js
. It has configurable options to not just make those files but also generate /static/js/myapp.min.a206ec6bd8c7.js.gz
which means that with gzip_static
in Nginx, Nginx doesn't have to Gzip compress static files on-the-fly but can basically just read it from disk. Nginx doesn't care how the file got there but an immediate advantage of preparing the file on disk is that the compression can be higher (smaller .gz
files). That means smaller responses to be sent to the client and less CPU work needed from Nginx. Your job is to set gzip_static on;
in your Nginx config (per location
) and make sure every compressable file exists on disk with the same name but with the .gz
suffix.
In other words, when the client does GET https://example.com/static/foo.js
Nginx quickly does a read on the file system to see if there exists a ROOT/static/foo.js.gz
and if so, return that. If the files doesn't exist, and you have gzip on;
in your config, Nginx will read the ROOT/static/foo.js
into memory, compress it (usually with a lower compression level) and return that. Nginx takes care of figuring out whether to do this, at all, dynamically by reading the Accept-Encoding
header from the request.
Zopfli
The best solution today to generate these .gz
files is Zopfli. Zopfli is slower than good old regular gzip
but the files get smaller. To manually compress a file you can install the zopfli
executable (e.g. brew install zopfli
or apt install zopfli
) and then run zopfli $ROOT/static/foo.js
which creates a $ROOT/static/foo.js.gz
file.
So your task is to build some pipelining code that generates .gz
version of every static file your Django server creates.
At first I tried django-static-compress
which has an extension to regular Django staticfiles storage. The default staticfiles storage is django.contrib.staticfiles.storage.StaticFilesStorage
and that's what django-static-compress
extends.
But I wanted more. I wanted all the good bits from django-pipeline
(minification, hashes in filenames, concatenation, etc.) Also, in django-static-compress
you can't control the parameters to zopfli
such as the number of iterations. And with django-static-compress
you have to install Brotli
which I can't use because I don't want to compile my own Nginx.
Solution
So I wrote my own little mashup. I took some ideas from how django-pipeline
does regular gzip
compression as a post-process step. And in my case, I never want to bother with any of the other files that are put into the settings.STATIC_ROOT
directory from the collectstatic
command.
Here's my implementation: peterbecom.storage.ZopfliPipelineCachedStorage. Check it out. It's very tailored to my personal preferences and usecase but it works great. To use it, I have this in my settings.py
: STATICFILES_STORAGE = "peterbecom.storage.ZopfliPipelineCachedStorage"
I know what you're thinking
Why not try to get this into django-pipeline
or into django-compress-static
. The answer is frankly laziness. Hopefully someone else can pick up this task. I have fewer and fewer projects where I use Django to handle static files. These days most of my projects are single-page-apps that are 100% static and using Django for XHR requests to get the data.