Reset static files cache each version (Django)

Ideally, you'd want your static files to be cached so that clients don't need to request them at all. In Apache, you can achieve this with:

ExpiresActive on
ExpiresDefault "access plus 1 year"
Header append Cache-Control public

But you also want the clients to request the new version as soon as anything changes. Since this can't be achieved with cache headers (they'll not request the files, so they never get the header), you can change the name instead. There's a Django static files storage class for that, which you can set with:

STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'  # or CachedStaticFilesStorage'

Then make sure that you use:

{% load static from staticfiles %}
{% load 'filename.png' %}

The difference between ManifestStaticFilesStorage and CachedStaticFilesStorage is that the former stores hashes in a json file and the later in the cache backend (manifest is probably faster and easier).

This system is only active when not in debug mode, in which case you're serving static files through something like Apache from STATIC_ROOT.

Simple alternative

The problem I had with this is that I can't get it to work with djangocms and filer. I get errors about files not found when I run any management command, including collectstatic.

So here's a trivially simple alternative, if your VERSION is an url-safe string:

if DEBUG:
        STATIC_URL = '/static/{0:}/'.format(VERSION)  # must have trailing /
else:
        STATIC_URL = '/static/'
        STATIC_ROOT = join(STATIC_ROOT, VERSION)  # so STATIC_ROOT must already be set

This simply stores a new copy of all static files in a subdirectory of STATIC_ROOT. This will cause the path to change every time that VERSION changes (and you run collectstatic if DEBUG=False), causing a reload. Relative references to static files still work (e.g. images references from css).

The minor downside is that all static files are refreshed for each new version, even though only a fraction of them might have changed. I feel that many browsers clear cache more often than many websites launch new versions, so I find it acceptable. You could opt to use only the first two parts of version ("3.14" instead of "3.14.159") to refresh only on important changes.

Personally I use this slightly different one, since my VERSION is of the form (3, 14, 159) (three unsigned tiny-ints):

from base64 import urlsafe_b64encode
from struct import pack

VERSION_HASH = urlsafe_b64encode(pack('=I', 65536*VERSION[0] + 256*VERSION[1] + VERSION[2])).rstrip(b'=').rstrip(b'A')
if DEBUG:
        STATIC_URL = '/static/{0:}/'.format(VERSION_HASH.decode('ascii'))
        STATIC_ROOT = join(BASE_DIR, 'dev', 'static')
else:
        STATIC_URL = '/static/'
        STATIC_ROOT = join(BASE_DIR, 'dev', 'static', VERSION_HASH.decode('ascii'))

The urls are a little shorter and don't contain the version in plain text (though it can be derived).

Comments

No comments yet

You need to be logged in to comment.