Every site has assets, and Django's static pipeline handles them differently in development and production.
Static files are assets shipped with your code: CSS, JavaScript, images. The django.contrib.staticfiles app (installed by default) finds them in:
- each app's
static/folder, namespaced like templates:blog/static/blog/site.css - extra directories listed in
STATICFILES_DIRS, for project-wide assets
In templates, load the tag library and build URLs with {% static %}:
{% load static %}
<link rel="stylesheet" href="{% static 'blog/site.css' %}">The URL is based on STATIC_URL (default "static/"). In development, runserver serves static files automatically when DEBUG=True.
In production, Django does not serve them. Run python manage.py collectstatic, which copies everything into STATIC_ROOT, and let something efficient serve that folder: Nginx, a CDN, or the WhiteNoise package inside your app.
Configure ManifestStaticFilesStorage (in STORAGES["staticfiles"]) to give files hashed names like site.3f2a1c.css, so browsers can cache them forever and still get new versions after a deploy. That only works if every reference uses {% static %}.
Media files are different: files uploaded by users. They use MEDIA_URL and MEDIA_ROOT (or a cloud storage backend) and must never be mixed with static files, because user uploads are untrusted.
Example
# mysite/settings.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "assets"] # project-wide files
STATIC_ROOT = BASE_DIR / "staticfiles" # collectstatic target
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"},
}
MEDIA_URL = "media/" # user uploads, kept separate
MEDIA_ROOT = BASE_DIR / "media"
# template:
# {% load static %}
# <img src="{% static 'blog/logo.png' %}" alt="logo">
# deploy:
# python manage.py collectstatic --noinputRemember
- App assets live in app/static/app/; link them with {% static %}
- collectstatic gathers files into STATIC_ROOT for the web server or CDN
- Media (user uploads) are separate from static files
Watch out for
- Hard-coding /static/... paths, which breaks hashed filenames and CDN URLs.