Django Static Files and Templates
Static files like CSS, JavaScript, and fonts are a core piece of any modern web application. Django provides tremendous flexibility around how these files are used, however this often leads to confusion for newcomers. For local development, the Django web server will serve static files and minimal configuration is required. For production, the external package WhiteNoise and several additional steps are required.
In this tutorial, we'll look at how to configure static files for local development and then in production.
Local Development
A single Django project often contains multiple apps. By default, Django will look within each app for a static
directory containing static files. So if one of your apps was called blog
and you wanted to add a CSS file to it called base.css
, you need to first create a new directory called blog/static
and then add the file within it so the location would be blog/static/style.css
.
However, since most Django projects involve multiple apps and shared static files, it is commonplace to instead create a top-level folder typically named static
. This can be added from the command line with the mkdir
command:
$ mkdir static
If you look within the settings.py
file, near the bottom, there is a single line of configuration for static files called STATIC_URL, which is the location of static files in our project.
# settings.py STATIC_URL = '/static/'
This means that static files will be stored in the location http://127.0.0.1:8000/static/
or http://localhost:8000/static/
. In order to notify Django of our new top-level static
folder, we must add a configuration for STATICFILES_DIRS telling Django to also look within a static
folder.
# settings.py STATIC_URL = '/static/' STATICFILES_DIRS = (str(BASE_DIR.joinpath('static')),) # new
Note that this example uses pathlib, loaded by default for Django 3.1+. If you are using a settings.py
file generated by 3.0 or before, you'd use STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
.
Within the new static
folder, create subfolders for each type of static file such as css
, js
, and img
. On the command line, these can be added using the mkdir
command:
$ mkdir static/css $ mkdir static/js $ mkdir static/img
Now you can add all your static assets. So the base.css
file would be located in static/css/base.css
. If you run the local webserver at this point--python manage.py runserver
--the styling won't be available though! And that is because static assets must be explicitly loaded into the templates.
Loading static files into Templates
Loading static files in a template is a two-step process:
- add
{% load static %}
at the top of the template - add the {% static %} template tag with the proper link
Let's assume you are using a base.html
file in your Blog project. Making both updates would look as follows:
<!-- templates/base.html --> {% load static %} <!DOCTYPE html> <html> <head> <title>Learn Django</title> <link rel="stylesheet" href="{% static 'css/base.css' %}"> </head> ... </html>
If you save and reload the web page, you'll see the changes. Adding links for either images in an img
folder or JavaScript in a js
folder would look as follows:
<img src="{% static 'img/path_to_img' %}"> <script src="{% static 'js/base.js' %}"></script>
collectstatic
The local Django server will serve static files but a production server such as Gunicorn or uWSGI will not. Therefore, Django comes with the built-in collecstatic
command that compiles all static files into a single directory suitable for deployment into production. The STATIC_ROOT configuration sets the absolute location of these collected files, typically called staticfiles
. The final piece is STATICFILES_STORAGE, which is the file storage engine used when collecting static files with the collecstatic
command. By default, it is implicitly set to django.contrib.staticfiles.storage.StaticFilesStorage
.
The end result is that there are four configurations required for static files and the python manage.py collectstatic
command must be run each time there is a change to re-compile all the static assets into a single directory.
Update your settings.py
file as follows:
# settings.py STATIC_URL = '/static/' STATICFILES_DIRS = (str(BASE_DIR.joinpath('static')),) # new STATIC_ROOT = str(BASE_DIR.joinpath('staticfiles')) # new STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' # new
Then run the command python manage.py collectstatic
.
(env) $ python manage.py collectstatic
A new staticfiles
directory will be created which has folders for admin
(the built-in admin has its own static files), staticfiles.json
, and whatever directories are in your static
folder.
If you now add a new static file to static
, it will be available for local usage. It is only for production where the file won't be present, unless you run python manage.py collectstatic
each and every time. For this reason, running collectstatic
is typically added to deployment pipelines and is done by default on Heroku.
As a brief recap:
STATIC_URL
is the URL location of static files located inSTATIC_ROOT
STATICFILES_DIRS
tells Django where to look for static files in a Django project, such as a top-levelstatic
folderSTATIC_ROOT
is the folder location of static files whencollecstatic
is runSTATICFILES_STORAGE
is the file storage engine used when collecting static files with thecollecstatic
command.
Production
Even though we've configured our Django project to collect static files properly, there's one more step involved which is not included in the official Django docs. That is the configuration of WhiteNoise to serve the static files in production. The first step is to install the latest version of the package:
(env) $ pipenv install whitenoise==5.1.0
Then in the settings.py
file make three changes:
- add
whitenoise
to theINSTALLED_APPS
above the built-instaticfiles
app - under
MIDDLEWARE
, add a newWhiteNoiseMiddleware
on the third line - change
STATICFILES_STORAGE
to useWhiteNoise
.
It should look like the following:
# settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'whitenoise.runserver_nostatic', # new 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # new 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ... STATIC_URL = '/static/' STATICFILES_DIRS = (str(BASE_DIR.joinpath('static')),) STATIC_ROOT = str(BASE_DIR.joinpath('staticfiles')) STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' # new
That's it! Run python manage.py collectstatic
again so that the files are stored using WhiteNoise. And then deploy with confidence to the hosting platform of your choice.