Guide: Sitemaps

SeoSitemap is a thin subclass of Django's django.contrib.sitemaps.Sitemap. Its key property: the <loc> URL for each item is taken from the item's resolved SEO canonical — so the sitemap URL always matches the page's rel="canonical". This eliminates a common hard-to-spot SEO bug where sitemap URLs and canonical tags diverge.


Basic sitemap

# sitemaps.py
from seo_suite.sitemaps import SeoSitemap
from .models import Article


class ArticleSitemap(SeoSitemap):
    changefreq = "weekly"
    priority = 0.8

    def items(self):
        return Article.objects.filter(published=True)
# urls.py
from django.contrib.sitemaps.views import sitemap
from .sitemaps import ArticleSitemap

sitemaps = {"articles": ArticleSitemap}

urlpatterns = [
    path("sitemap.xml", sitemap, {"sitemaps": sitemaps}),
]

Automatic lastmod

SeoSitemap checks the following attributes on each item in order and uses the first one it finds as <lastmod>:

updated_atmodifieddate_modifiedupdatedlast_modified

If none of these attributes exist, <lastmod> is omitted. Models with a common updated_at = DateTimeField(auto_now=True) work without extra setup.


Multilingual sitemap with hreflang alternates

Use Django's built-in sitemap i18n attributes alongside SeoSitemap:

class ArticleSitemap(SeoSitemap):
    i18n = True
    alternates = True
    x_default = True
    changefreq = "weekly"

    def items(self):
        return Article.objects.filter(published=True)

With i18n = True, Django generates one <url> per item per active language. alternates = True adds <xhtml:link rel="alternate"> entries for each language. x_default = True adds the x-default alternate.

Because each <loc> comes from the object's get_seo_metadata().canonical_url (via SeoSitemap.location()), the canonical URL in the sitemap is always in sync with the canonical URL rendered on the page.


How SeoSitemap.location works

For an item that has SeoModelMixin, location() calls get_seo_metadata() and extracts canonical_url. Only the path and query string are used (scheme and host are added by Django's sitemap framework from the current Site object).

If the item has no get_seo_metadata(), or if canonical_url is not set, location() falls back to Django's default behaviour (calling item.get_absolute_url()).


Multiple sitemaps

Combine several sitemaps in urls.py:

from django.contrib.sitemaps.views import sitemap
from .sitemaps import ArticleSitemap, CategorySitemap, PageSitemap

sitemaps = {
    "articles": ArticleSitemap,
    "categories": CategorySitemap,
    "pages": PageSitemap,
}

urlpatterns = [
    path("sitemap.xml", sitemap, {"sitemaps": sitemaps}),
]

Sitemap index

For large sites, use Django's SitemapIndexSite or just split sitemaps normally — SeoSitemap has no restrictions on the number of items.


Static pages in a sitemap

For model-less pages, create a simple sitemap that returns paths:

from django.contrib.sitemaps import Sitemap


class StaticViewSitemap(Sitemap):
    priority = 0.5

    def items(self):
        return ["/about/", "/contact/", "/privacy/"]

    def location(self, item):
        return item

SeoSitemap is only needed when items have get_seo_metadata(). For plain static paths, Django's built-in Sitemap is sufficient.


Pointing robots.txt at your sitemaps

Once your sitemap URL is stable, add it to ROBOTS_SITEMAP_URLS so crawlers can always find it via robots.txt:

SEO_SUITE = {
    "ROBOTS_SITEMAP_URLS": ["https://example.com/sitemap.xml"],
}

The URL is appended as a Sitemap: directive to every served robots.txt without requiring you to edit version content. See the versioned robots.txt guide for the full workflow.