Additional Pages¶
Most of the time you want every page to live inside the toctree so it shows up in the sidebar and participates in previous/next navigation. Sometimes, though, you want a page that is published as HTML but deliberately not in the tree: a 404 page, a marketing splash, a standalone changelog linked only from the navbar, an HTML redirect. This page covers the three mechanisms Sphinx exposes for that.
Orphan pages¶
The simplest case: a Markdown page that lives in your source tree and is built like every other page, but is not listed in any toctree. Normally Sphinx warns about this (“document isn’t included in any toctree”) because it usually means you forgot to wire up the page.
If you want the warning suppressed for a specific page, mark it as an
orphan. In a MyST file, add the :orphan: metadata at the very top,
before the H1:
---
orphan: true
---
# Standalone page
This page exists but is not in the sidebar.
The page builds normally, is reachable at its usual URL, and Sphinx does not complain. Use orphan pages for things you link to from other places — a landing page linked from your navbar, an internal cheat sheet linked from a blog post — but do not want cluttering the main navigation.
You can also set hide-toc: true as part of the same front matter if
the orphan page should suppress the right-hand table of contents:
---
orphan: true
hide-toc: true
---
# Landing
html_additional_pages¶
When you need to ship arbitrary HTML — not Markdown, not RST — use the
html_additional_pages mapping in conf.py. The keys are output
paths (without the .html extension), and the values are Jinja2
template names that Sphinx renders to produce the page.
html_additional_pages = {
"404": "404.html",
"robots": "robots.txt",
}
With this, Sphinx renders _templates/404.html into the build output
as 404.html, and _templates/robots.txt into robots.txt. The
templates have access to the full Jinja context Sphinx provides to
layout templates, so you can extend the base theme to get the navbar
and footer for free:
{% extends "layout.html" %}
{% set title = "Page not found" %}
{% block body %}
<div class="hx-content">
<h1>Page not found</h1>
<p>The page you were looking for does not exist.</p>
<p><a href="{{ pathto(master_doc) }}">Return home</a>.</p>
</div>
{% endblock %}
Make sure templates_path = ["_templates"] is set in conf.py so
Sphinx can find the template file.
HTML redirects¶
A common special case of html_additional_pages is a redirect. When
you rename a page, you want URLs that used to point at the old name
to still work. Create a small redirect template:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0; url={{ target }}">
<link rel="canonical" href="{{ target }}">
</head>
<body>
<p>Redirecting to <a href="{{ target }}">{{ target }}</a>.</p>
</body>
</html>
Then register the redirects in conf.py. You will need to pass the
target through to the template, which means either a wrapper function
or using html_context:
html_additional_pages = {
"old-name": "redirect.html",
}
html_context = {"target": "new-name.html"}
For a more complete solution, install
sphinx-reredirects
which wraps this pattern in a friendlier config:
extensions = [..., "sphinx_reredirects"]
redirects = {
"old-name": "new-name.html",
}
Raw HTML inside a page¶
If you want arbitrary HTML embedded inside an otherwise normal
Markdown page, you do not need html_additional_pages at all — just
use a MyST raw-HTML block:
```{raw} html
<div class="my-custom-widget">
<iframe src="https://example.com/embed" loading="lazy"></iframe>
</div>
```
The raw block is emitted verbatim into the HTML output and skipped by
other builders. Use it sparingly — anything you embed this way will
not be picked up by search, by the intersphinx inventory, or by
sphinx-build -b epub.