Angry Bits

Words on bytes and bits

Django: serving hashed static files with Nginx

If you are using Django, you certainly know about a nice feature that was added to since 1.4 for better caching static files. I'm talking about the CachedStaticFilesStorage (doc), a new storage which saves static files with also their hashed content values appended to the name.

Why is it useful?

CachedStaticFilesStorage lets you serve files with a unique name that depends on their contents. This makes possible to extend the TTL of the cache for these files infinitely. Max cache TTL means faster loading for returning visitors and users that visit more than a single page which share common resources (javascripts, css styles, etc...) and it also reduce the bandwith requirements for your server.

If the content changes the file will have another name. Let's see how it works with an example.

You have a file style.css in your static assets. During the collectstatic run you will get also a style.HASH.css where HASH is something like 1234567890ab, a twelve digits hex number computed as an hash of the file content.

How to use it?

In order to use this storage you have to set the STATICFILES_STORAGE in your settings.py

STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'

If you have a custom storage, you can inherit from the CachedFilesMixin class to take advantage of it.

from django.contrib.staticfiles.storage import CachedFilesMixin

class CachedCustomStorage(CachedFilesMixin, CustomStorage):
    pass

The staticfiles library provides also a static tag that can be used in your template to get the hashed name of the file:

{% load staticfiles %}
{% static "style.css" %}

Extending Nginx static cache

Finally we want to take advantage of the hashed file names on the web server side. If you use nginx as I do, you can just grab this snippet:

location /static {
    alias /my/app/path/static;
    if ($uri ~* ".*\.[a-f0-9]{12,}\.(css|js|png|jpg|jpeg|gif|swf|ico)" ) {
       expires max;
    }
}

Put this snippet into your server block. Change the alias parameter with the path of your static file folder and extend the list of file extensions if you need more.

Cache the hash!

Each time the static file name resolution is called it computes the hash of the file content. This needs time but fortunately we can optimize this step enabling caching. You can do it by simply adding a cache backend for staticfiles in Django's settings:

CACHES = {
    # ... your other caches here ...

    'staticfiles': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'staticfiles',
        'TIMEOUT': 3600 * 24 * 8,
        'MAX_ENTRIES': 1000,
    }
}

I highly suggest to use the local memory cache backend because of the limited memory requirements, this way you can drop the communication overhead of other backends.

Remember to tune the MAX_ENTRIES and the TIMEOUT parameters.

Comments