Nikola Static Site Generator: Sections Configuration
Nikola's sections feature is one of the less-documented parts of a generator that otherwise has thorough reference material. The core idea is to subdivide a site's post content into named sections — each with its own listing page, feed, and URL prefix — without forcing every post through a single flat blog archive. This page covers how sections are configured in conf.py, how they affect output URLs, how sections relate to and differ from tags, and where the sections model is genuinely useful versus where it adds configuration overhead for marginal benefit. The notes reflect observed build behaviour from a real multi-section site, not just the documentation surface. This sits within the broader developer notes section; for background on content movement between static generators and WordPress, the static to WordPress migration notes cover adjacent ground.
What sections are
In Nikola, a section is a named grouping defined in the POSTS configuration tuple. Each section has its own source directory, output directory, and listing template. Posts placed in a section's source directory inherit that section's URL prefix and appear in that section's index rather than the default blog listing. Sections are structural — they affect URL layout and listing organisation. Tags are orthogonal metadata that cross-cut sections: a post in the "howto" section can carry any tags, and those tags appear in the global tag cloud alongside tags from other sections.
The POSTS tuple in conf.py defines each section as a row specifying source glob, destination directory, listing template, and optionally a section name:
POSTS = (
("posts/*.rst", "blog", "post.tmpl"),
("howto/*.md", "howto", "post.tmpl"),
("reviews/*.md", "review", "post.tmpl"),
)
Each row is an independent content pipeline. Posts in howto/ render to /howto/post-slug/index.html, generate their own /howto/index.html listing, and produce their own /howto/rss.xml feed if RSS generation is configured. There is no cross-contamination between sections in listing pages — the blog index stays clean.
Sections vs tags: when each makes sense
Tags and sections solve different problems. Tags are keyword classifiers: one post can carry many tags, they share a global namespace, and they're most useful for cross-cutting facets — "all posts touching TLS, regardless of content type." Sections are structural containers: a post belongs to one section, the section shapes the URL, and sections are most useful when content types have genuinely distinct reader expectations.
The sections model works well when content types serve different modes of use. A how-to post is scanned for steps; a review is read linearly for assessment; a developer note is often reference material returned to repeatedly. A single flat blog archive mixes these poorly. Separate sections let each content type have its own listing, pagination, and feed — a reader subscribed to the "howto" feed gets only procedural guides, not reviews or commentary mixed in.
Tags remain valuable for cross-section discovery. A "privacy" tag spanning how-to posts, security analysis, and reviews creates a useful index that section organisation alone cannot produce. The two systems are additive: sections define structure, tags define semantics.
Feed and index path configuration
The SECTIONS_INDEX_PATH variable controls where each section's index page lands. Without explicit configuration, Nikola uses the destination directory name from the POSTS tuple. Feed generation per section requires GENERATE_RSS = True.
SECTIONS_INDEX_PATH = {
"blog": "blog/index.html",
"howto": "howto/index.html",
"review": "review/index.html",
}
GENERATE_RSS = True
# Section feeds appear at /{section}/rss.xml automatically
A common collision point is the interaction between sections and the TAG_PATH configuration. Tag listing pages land at /categories/ by default and cover all sections. If any section name or destination directory collides with the tag output path, Nikola silently overwrites one set of files with the other during build. Rename either the section destination or the TAG_PATH value before this causes data loss in the build output — it will not raise an error.
Section-specific templates
The third element of each POSTS row is the template filename. Each section can use a different template:
POSTS = (
("posts/*.rst", "blog", "post.tmpl"),
("howto/*.md", "howto", "howto-post.tmpl"),
("reviews/*.md", "review", "review-post.tmpl"),
)
Custom templates for review posts can include structured fields — rating, tested version, platform — that don't belong in a standard post template. This is one of the practical reasons to use sections over a single template with conditional per-post metadata: the template differentiation is structural rather than a pile of if blocks inside one template file.
Section-specific templates must exist in the theme's templates/ directory. Using a template name that doesn't exist fails silently in some Nikola versions — the site builds using the default fallback template with no warning in the console output. Test new section template names with nikola build --invariant and verify the output HTML contains section-specific markup rather than the generic post layout.
Practical assessment
Sections add configuration overhead that pays off when a site genuinely needs distinct URL namespaces for different content types. For a personal site with one homogeneous stream of posts, sections complicate setup without meaningful benefit — tags are sufficient. For a site that is simultaneously a how-to guide collection, a review archive, and a developer notebook, sections produce a structure that matches how the content is actually used and indexed.
The one area where sections consistently trip up new Nikola users is the interaction with RSS, tag archives, and search configuration — each feature that works globally against "all posts" needs to be told whether to aggregate across sections or operate per-section. That configuration is explicit rather than implicit, which means no surprises once you've done it, but a non-trivial amount of conf.py work upfront.