DjangoCMS and ManifestStaticFilesStorage

The expires header in HTTP works like the TTL (Time to live) for DNS caching – once cached, the browser wont request the asset anymore until its individual cache-until date expires.

If the file content changes on the server, the browser will not see those changes before the expiration date of the file’s cache.

That can cause problems, for example when jacascript or styles in the cached file dont match the html code of the document anymore.

Also, when we deploy, we want the new version to be visible right away, to everyone.

In order to have both browser caching AND full version control in the user’s browser, the idea is to add a unique hash to the filename of all files that are collected by django’s staticfiles. So that if they are changed, the filename changes. Like this, a new asset is fetched instantly by the browser as caching is done by filename.

The expiry header can then safely be set to an “infinite” date (a date far in the future) – we therefore call this “far future expiry header”.

  • You can do this by setting far future expiry header on the static directory, where django puts the collected static files. This is done in the configuration of the webserver that serves the static files (normally nginx).
  • https://docs.djangoproject.com/en/2.1/ref/contrib/staticfiles/#ManifestStaticFilesStorage
  • Allows to remove hashing from the webpack setup which is convenient
  • Allows to also hash images via {% static 'path/to/asset.jpg' %}, respectively assets that are not bundled via webpack
  • Hashing is only active if debug is False
  • On divio.com, if you are using aldryn-django then static file hashing can be activated like this: https://docs.divio.com/en/latest/reference/addons-aldryn-django.html#hash-static-file-names

How to fix collectstatic if there are errors

  • The collectstatic will fail if any app that includes static assets in templates is not in INSTALLED_APPS
  • On production, collectstatic will also sometimes fail, in such cases, DEBUG has to be turned on, ./manage.py collectstatic has to be run, then DEBUG can be turned off again. See https://github.com/divio/djangocms-text-ckeditor/issues/474
  • If you use inlined data urls and the svg code itself uses url()s and these contain url_encoded values, then collectstatic command will fail because it will try to analyse these url()s and you have to work around this like https://code.djangoproject.com/ticket/21080#comment:12
  • you cannot use absolute URLs that are otherwise accepted by Django. This works {% static "my_app/example.jpg" %} but this will not {% static "/my_app/example.jpg" %}

How to test locally in production mode

ManifestStaticFilesStorage is only activated if DEBUG = False. However disabling debug mode in django will stop the runserver from serving the static files. There might also be a couple of other issues that you need to resolve before you can see ManifestStaticFilesStorage in action on your local dev env:

  • on Divio projects, set these env vars in .env-local:
DEBUG=False
STAGE=live
DIVIO_ENV=live
  • Set SECURE_SSL_REDIRECT to False in your settings.py
  • Set HTTP_PROTOCOL to http in your settings.py
  • Add the following to your urls.py:
from django.conf.urls.static import static


...) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Now you can build your frontend (something like yarn run build) and then collect the static files using ./manage.py collectstatic and you will be running with ManifestStaticFilesStorage enabled on your local dev env system!