21.3 Building the Base Template
Right. You’ve decided to build a theme from scratch. Good for you. This is where you stop being a tourist and start being a citizen. You’ll get your hands dirty, you’ll make mistakes, and you’ll learn more in the next ten minutes than you would from installing fifty pre-made themes. The base template is the bedrock. It’s the single most important file in your theme. Get this right, and everything else slots into place. Get it wrong, and you’ll be chasing weird bugs for weeks.
Think of base.html as the skeleton of your site. Every other template (your page templates, your post templates) is just a specific variation of this skeleton. It holds everything that’s consistent across every page: the <html> declaration, the <head>, the site header, the footer, the navigation. Your job is to build this skeleton with placeholders—blocks—where the unique content for each page will go.
The Absolute Non-Negotiable Basics
Let’s start with the bare minimum. This isn’t a suggestion; it’s the law. Every Django template must inherit from a base template, and every base template must start with this. We’ll build up from here.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Amazing Site{% endblock %}</title>
</head>
<body>
<main>
{% block content %}
<!-- The unique content for each page goes here -->
{% endblock %}
</main>
</body>
</html>
See those {% block %} tags? That’s the magic. The title block provides a sensible default (“My Amazing Site”) but any child template can override it. The content block is the big one—it’s the empty canvas where the unique body of each page will be painted.
Building a Realistic Base Template
The example above is criminally simplistic. Let’s make something that might actually be used on a real website.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Amazing Site{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/main.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<h1><a href="{% url 'home' %}">My Brilliant Blog</a></h1>
<nav>... navigation code here ...</nav>
</header>
<main>
{% block content %}
{% endblock %}
</main>
<footer>
<p>© {% now "Y" %} My Brilliant Blog. All rights... reserved, I guess?</p>
</footer>
<script src="{% static 'js/main.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
Now we’re cooking. Notice the strategic choices. We’re loading the static files. We’re using the {% url %} tag for the home link—never hardcode URLs, unless you enjoy refactoring nightmares. We’ve added extra_css and extra_js blocks. This is a critical best practice. It allows child templates to inject specific stylesheets or scripts only on the pages that need them, without bloating every single page.
The Block Superpower: Overrides and Extensions
Here’s how a child template, say blog/post_detail.html, uses this base. The {% extends %} tag is the key—it tells Django, “This template is a child of that base.”
{% extends "base.html" %}
{% block title %}{{ post.title }} | My Amazing Site{% endblock %}
{% block content %}
<article>
<h2>{{ post.title }}</h2>
<div class="post-content">
{{ post.content|safe }}
</div>
</article>
{% endblock %}
{% block extra_js %}
<script src="{% static 'js/comments.js' %}"></script>
{% endblock %}
Beautiful, isn’t it? The child template only defines what’s different. It overrides the title block to use the post’s title (note the pipe | for adding a site identifier, a classic SEO move). It populates the content block with the post. And it uses the extra_js block to load a comment script only on this page, keeping the rest of the site lean.
Common Pitfalls and How to Avoid Them
Forgetting
{% load static %}in the base template. It seems obvious, but you’ll do it. You’ll get a nastyInvalid block tagerror and scratch your head for ten minutes. Just remember: if you’re using{% static %}, you must{% load static %}first, in that template.Not using
{% url %}tags. I said it before, I’ll say it again. Hardcoding/or/about/is a ticking time bomb. Use{% url 'app_name:url_name' %}. Always.Overcomplicating blocks. You can create a block for every single element, but don’t. You’ll end up with a fractal nightmare of overrides. Start with the big ones:
title,content,extra_css,extra_js. Create new blocks only when you have a concrete, repeating need for them.Ignoring the
{% block.super %}power. Sometimes you don’t want to replace a block, you want to add to it. This is most common with CSS/JS and thetitleblock. Imagine you want every page’s title to end with " | My Site". You’d do this in the child:{% block title %}{{ post.title }} | {{ block.super }}{% endblock %}This would render as “My Post Title | My Amazing Site”.
{{ block.super }}is the value from the parent block. It’s incredibly useful. Use it.