Static files are CSS, JavaScript, images, and fonts. Django handles them in a specific way: it serves them itself during development, but in production it requires a prior collection step.
Folder Structure
devblog/
static/ # app-level static files (next to the app)
css/
main.css
js/
main.js
images/
logo.png
templates/
base.html
Or static files at the project level:
devblog/
assets/ # project-wide static files
css/
js/
devblog/
settings.py
Settings in settings.py
# URL the browser uses to request static files
STATIC_URL = '/static/'
# Additional folders to search (besides <app>/static/)
STATICFILES_DIRS = [
BASE_DIR / 'assets',
]
# Where collectstatic places files for production
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS — extra folders Django scans when resolving {% static %} tags. STATIC_ROOT is only used by the collectstatic command.
{% load static %} and
Load the tag once at the top, then use it anywhere in the file:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<!-- CSS -->
<link rel="stylesheet"
href="{% static 'css/main.css' %}">
<!-- External library (CDN — no {% static %} needed) -->
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
<!-- Image -->
<img src="{% static 'images/logo.png' %}" alt="Logo" width="120">
<!-- JS before </body> -->
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
{% static 'css/main.css' %} generates a URL like /static/css/main.css. If STATIC_URL ever changes, all paths update automatically.
Complete base.html with Static Files
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}DevBlog{% endblock %}</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="{% static 'css/main.css' %}">
<link rel="icon" href="{% static 'images/favicon.ico' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="{% static 'js/main.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
collectstatic
Before deploying, collect all static files into one folder (STATIC_ROOT):
python manage.py collectstatic
Django walks through all <app>/static/ directories and STATICFILES_DIRS, copies everything into STATIC_ROOT. Nginx or a CDN then serves files from that folder directly, bypassing Django.
Add staticfiles/ to .gitignore:
# .gitignore
staticfiles/
Development vs Production
| Aspect | Development | Production |
|---|---|---|
| Who serves files | Django (runserver) |
Nginx / CDN |
collectstatic needed? |
No | Yes |
DEBUG |
True |
False |
STATIC_ROOT |
Not used | Folder for Nginx |
In development, Django automatically looks up files in <app>/static/ and STATICFILES_DIRS when DEBUG=True. In production with DEBUG=False, Django does not serve static files — that is the web server’s job.
Example CSS Structure for DevBlog
/* static/css/main.css */
:root {
--color-primary: #0d6efd;
--color-text: #212529;
}
body {
font-family: 'Inter', sans-serif;
color: var(--color-text);
}
.post-body pre {
background: #f8f9fa;
padding: 1rem;
border-radius: 0.375rem;
overflow-x: auto;
}
.post-body img {
max-width: 100%;
height: auto;
}
💬 Comments (0)
No comments yet
Be the first to share your opinion about this article!