Sitemaps & SEO

Search engines find pages by following links and reading a sitemap, not by guessing. Django ships a small framework, django.contrib.sitemaps, that generates a standards-compliant XML sitemap straight from your own models — no hand-maintained list of URLs that quietly goes stale the moment someone adds a new page.

Enabling the Sitemaps Framework

Like most of Django's optional pieces, sitemaps start as an app you add to INSTALLED_APPS:

# taskflow_project/settings.py
INSTALLED_APPS = [
    # ...
    'django.contrib.sitemaps',
]

Defining a Sitemap Class

A sitemap is a plain Python class inheriting from Sitemap, with two methods Django calls for you: items(), returning the QuerySet of objects to include, and location(obj), returning each one's URL. Most of TaskFlow's tasks belong to private projects, but suppose a subset are flagged public and worth exposing to search engines:

# taskflow/sitemaps.py
from django.contrib.sitemaps import Sitemap
from .models import Task

class TaskSitemap(Sitemap):
    changefreq = "weekly"
    priority = 0.5

    def items(self):
        return Task.objects.filter(is_public=True)

    def location(self, obj):
        return f"/tasks/{obj.pk}/"

Django calls items() once to get every object to list, then calls location() for each one to work out its URL. Add a new public task tomorrow, and it appears in the sitemap on the very next request — nobody has to remember to update a static file.

changefreq and priority

Both class attributes above are hints, not guarantees — search engines are free to ignore them, but they're cheap to provide and can influence crawl behavior. changefreq tells a crawler roughly how often a given URL's content tends to change ("always", "daily", "weekly", "yearly"...), and priority is a number from 0.0 to 1.0 expressing this URL's importance relative to other URLs in the same sitemap — it isn't compared across different sites.

Wiring the Sitemap into urls.py

A Sitemap class does nothing until a URL actually serves it. Django provides a ready-made sitemap view for exactly this — map it to the conventional /sitemap.xml path, and hand it a dictionary of every sitemap section your project has (here, just one: tasks):

# taskflow_project/urls.py
from django.contrib.sitemaps.views import sitemap
from taskflow.sitemaps import TaskSitemap

sitemaps = {'tasks': TaskSitemap}

urlpatterns = [
    # ... existing paths ...
    path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
         name='django.contrib.sitemaps.views.sitemap'),
]

Visiting /sitemap.xml now returns a proper XML document listing every public task's URL, along with its changefreq and priority — exactly what a search engine expects to find at that conventional address.

Basic On-Page SEO with Template Blocks

A sitemap tells a search engine which pages exist; the page-specific <title> and meta description tell it (and the visitor scanning search results) what each one is. Recall base.html from Template Inheritance & Includes — wrapping its <title> in a block lets every page override it individually instead of every page sharing one fixed title:

{# taskflow/templates/base.html #}
<head>
  <title>{% block title %}TaskFlow{% endblock %}</title>
  <meta name="description" content="{% block meta_description %}Manage projects and tasks with TaskFlow.{% endblock %}">
</head>
{# taskflow/templates/taskflow/task_detail.html #}
{% extends "base.html" %}

{% block title %}{{ task.title }} - TaskFlow{% endblock %}
{% block meta_description %}{{ task.description|truncatewords:20 }}{% endblock %}

{% block content %}
  <h1>{{ task.title }}</h1>
{% endblock %}

Every page still shares one base.html, but each one now announces its own specific title and description in search results, instead of every task page looking identical to a search engine.

Canonical URLs

A <link rel="canonical"> tag tells a search engine which URL is the "official" one for a page's content, which matters when the same content is reachable through more than one URL (with and without a trailing slash, or via a query string, for instance) — the same pattern this very tutorial site uses on every one of its own pages:

{# inside base.html's head, or overridden per page #}
<link rel="canonical" href="https://taskflow.example.com{{ request.path }}">

Does SEO Even Matter for an App Like TaskFlow?

Worth being honest here: most of TaskFlow sits behind a login. Search engines can't index a project board or someone's private task list, and building out sitemap and meta-tag machinery for pages nobody outside your own team will ever see is effort spent in the wrong place. Where this genuinely pays off is TaskFlow's small public surface — a marketing page, a public project's read-only view — and it pays off completely for a different kind of Django project built to be found: a blog, a documentation site, a marketing site. The techniques are identical either way; only how much of your project actually needs them changes.

Note: This is correct, real sitemap and template code — but this website's own project doesn't run a Django server, so /sitemap.xml isn't actually generated on this page. Add TaskSitemap to your own project's URLs to see the real XML output.
Next up: A sitemap and a title block are both about presenting content correctly to an audience — Internationalization covers presenting that same content correctly across different languages.