Static files: serving CSS and JavaScript correctly
Every template in this series has referenced no CSS at all — this part adds an actual stylesheet, and the Django-specific way of linking to it that keeps working after deployment changes where files are actually served from.
Static files settings
# blogsite/settings.py
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]STATIC_URL is the URL prefix static files are served under — static/style.css becomes /static/style.css in the browser. STATICFILES_DIRS is where Django looks for project-wide static files (as opposed to files inside a specific app's own static/ folder, which Django also finds automatically once the app is in INSTALLED_APPS).
mkdir -p static/css/* static/css/blog.css */
body { font-family: system-ui, sans-serif; max-width: 700px; margin: 2rem auto; }
article { margin-bottom: 2rem; }Linking it with {% static %}
{# blog/templates/blog/base.html #}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Blog{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>{% load static %} has to appear before {% static %} is used anywhere in the file — it's what makes the tag available at all, the same way a Python file needs an import before using what it imports. {% static 'css/blog.css' %} renders to /static/css/blog.css using STATIC_URL, rather than the path being hardcoded — change STATIC_URL later (or add a CDN prefix in production) and every {% static %} reference updates with it, with zero template edits.
Hardcoding href="/static/css/blog.css" instead of using . It works identically in development — until a production deployment serves static files from a CDN with a different URL prefix, and every hardcoded path in every template needs to be found and fixed by hand.
collectstatic, previewed
python manage.py collectstaticIn development, Django's dev server finds static files directly from STATICFILES_DIRS and each app's static/ folder — collectstatic isn't needed yet. It becomes necessary in production, where DEBUG = False turns that automatic serving off deliberately (part 22 covers why) and every static file needs to be gathered into one folder (STATIC_ROOT) that a real web server serves directly. Running it now, once, is just to see what it does before it's a required deployment step rather than a mysterious one.
Next: the same idea, for files a user uploads instead of files bundled with the project — a featured image on a post.