Skip to content
Edit this page
On this page

SEO and discoverability

Lumina automatically emits metadata that helps search engines index your docs and social platforms render shared links. As long as you set html_baseurl in your conf.py, you get:

3 min read
  • <meta name="description"> with smart fallbacks

  • <link rel="canonical"> on every page

  • Open Graph tags (og:title, og:description, og:image (with optional width/height), og:type, og:url, og:site_name, og:locale)

  • Twitter Card tags (twitter:card, twitter:title, twitter:description, twitter:image, twitter:site)

  • JSON-LD structured data (BreadcrumbList, TechArticle with ISO 8601 dates and a Google-conformant publisher.logo, WebSite + SearchAction)

  • A generated sitemap.xml — with per-URL <lastmod> when sphinx_last_updated_by_git is loaded

  • A default robots.txt pointing at the sitemap

Everything is on by default. There’s no enable flag.

Required: html_baseurl

Canonical URLs, sitemap entries, and the robots.txt Sitemap: line all need an absolute site URL. Set html_baseurl in conf.py:

html_baseurl = "https://docs.example.com/"

If you don’t set this, Lumina logs one warning at the start of the build and skips the URL-dependent features. The rest (description, OG title, Twitter card, etc.) still work.

Configuring social cards

Set a sitewide Open Graph image — the picture that appears when your docs are shared on Slack, Twitter/X, LinkedIn, etc.

html_theme_options = {
    "og_image": "og-card.png",  # filename in _static/, OR an absolute URL
    "og_image_alt": "MyProject — distributed task queue",
    "og_image_width": "1200",   # match your card; helps Slack/Facebook render instantly
    "og_image_height": "630",
    "twitter_site": "@myproject",  # optional; falls back to social_links
}

Aim for a 1200×630 PNG. Keep your logo and a short tagline in the top-left third (Twitter crops the right edge in some layouts). Declaring og_image_width / og_image_height lets Facebook, LinkedIn, and Slack reserve the slot before the image loads, so the preview never flashes a broken thumbnail.

Publisher logo (Rich Results)

Lumina’s TechArticle JSON-LD includes a publisher.logo for Google Rich Results. The publisher logo is not the same as og_image — Google expects it to be near-square (for example, 60×60 to 600×600), while a social card is a 1200×630 banner. Reusing the social card as a publisher logo can make the page ineligible for Rich Results.

Ship a small square logo and point Lumina at it:

html_theme_options = {
    "publisher_logo": "logo-square.png",  # filename in _static/, or absolute URL
}

The fallback chain is: publisher_logo → html_logo (when raster) → omit publisher.logo entirely (better to omit than to lie to Google).

Sitemap <lastmod>

When sphinx-last-updated-by-git is in your extensions list, Lumina automatically flips sitemap_show_lastmod = True so every URL in sitemap.xml carries the file’s last-commit date. <lastmod> is one signal Google uses to schedule recrawls, so it can help search engines notice content updates sooner.

extensions = [
    # ...
    "sphinx_last_updated_by_git",
]

If you’ve explicitly set sitemap_show_lastmod (in conf.py or via -D), Lumina respects your choice.

Per-page overrides via front matter

Any MyST page can override the defaults:

---
description: "Bypass the auto-extracted description with this exact text."
og_image: "custom-card.png"
og_type: "article"  # or "website"
keywords: "comma, separated, terms"
noindex: true       # hide from search engines and exclude from sitemap.xml
---

# Page title

All keys are optional.

Opting out

To turn off all SEO emission (no meta tags, no JSON-LD, no sitemap, no robots.txt):

html_theme_options = {
    "disable_seo": "true",
}

This is for niche cases — internal-only sites that don’t want to leak structured data, intranet portals, etc.

Replacing robots.txt

Lumina only writes robots.txt if one isn’t already at the build root. Drop your own into a directory and point html_extra_path at it:

html_extra_path = ["public"]   # public/robots.txt will win

How descriptions are picked

For each page, in priority order:

  1. description from front matter

  2. The first paragraph in the page (≥ 30 chars, skipping admonitions, code blocks, and toctrees), truncated to ~160 chars

  3. html_short_title (or project)

If none of those produce a usable string, no description meta is emitted (rather than ship a misleading default).

Verifying what’s emitted

After building, view the page source of _build/html/index.html (or any built page) and search for og: and application/ld+json to see the rendered output. Online validators worth a glance:

esc
↑↓ Navigate ↵ Open esc Close