EnglishNederlandsFrançais Toggle theme

Eleventy Baseline

Start building your site, skip the recurring setup work.
Table of Contents

head

INTERNAL_KEY: '_head'

The renderer is PostHTML (a small HTML AST tool that ships with Eleventy); Baseline owns the seed-building, the dedupe, the sort, and the placeholder replacement.


What it does

The head module composes a page's <head> in two stages, then replaces a <baseline-head> element you place inside your layout with the result. During the data cascade, it builds seeds: the per-page values the renderer needs (title, description, robots, canonical, head extras, locale). At transform-time, after Eleventy has rendered HTML, the PostHTML driver reads those seeds, builds hreflang alternates from the translation map when relevant, dedupes, sorts the elements by capo.js weights, and swaps the placeholder for the final <head>.

Active when

Always. The head module is always loaded.

Lifecycle

  • Cascade-time. The page-context registry builds per-page seeds: composed title, description, robots, canonical, generator, the <head> extras you configured, and the page's locale. Seeds get cached, keyed by page.url.
  • Transform-time. The PostHTML driver looks up the seeds for the current page, builds hreflang alternates from the translation-map store, emits the element list, dedupes meta and link entries, capo-sorts, and replaces <baseline-head> with a real <head>.

The two-stage split is necessary because Eleventy's HTML transformer only exposes page metadata, not the data cascade. The cascade-time pass caches what the transform-time pass needs.

Scope today

This pass covers what the spec calls bucket 1: charset, viewport, <title>, <meta name="description">, robots, canonical, optional generator, the user-supplied head.* extras (link, script, meta, style), and hreflang alternates when multilang is active.

Open Graph, Twitter cards, and JSON-LD are staged in code but not wired through to output yet. They are not part of this pass; do not configure them through the head module today. If you need them now, render them yourself through head.meta[] and head.link[] on the page or in settings.head.


The placeholder

Place a single <baseline-head> element inside your <head> tag. The driver replaces it with the composed element list:

<!doctype html>
<html lang="en">
	<head>
		<baseline-head></baseline-head>
	</head>
	<body>
		
	</body>
</html>

If the placeholder is missing, the driver does nothing and logs a warning. No error, no partial output.


How it works

  1. Cascade-time seed build. The page-context registry composes a seed object per page: site, page, entry, query, meta, render, head. See Page context for the shape.
  2. Transform-time lookup. The PostHTML driver runs once per page. It looks up the seeds by page.url. If no seeds exist (the page opted out via _internal: true, or has a non-HTML output), the driver logs and skips.
  3. Emit. Standard meta first (charset, viewport, title, description, robots, canonical, optional generator). Then user extras from seeds.head. Then hreflang alternates from the translation map.
  4. Dedupe. Meta and link entries are deduplicated; later entries (page-level) win over earlier ones (site-level).
  5. Sort. The list is sorted using capo.js element weights so the order in the final <head> follows the recommended ordering.
  6. Replace. The driver matches the <baseline-head> element in the PostHTML tree and swaps it for a real <head> with the sorted nodes inside.

Defaults

  • Title separator. ' – ' (en dash with surrounding spaces). Composed title shape: Page title – Site title.
  • Generator. Off. Set head.showGenerator: true to emit <meta name="generator"> with the Eleventy version.
  • Robots. Omitted unless the page (or settings.noindex) sets it to noindex. When set, emits <meta name="robots" content="noindex, nofollow">.
  • Canonical. Built from page.url plus settings.url and Eleventy's pathPrefix. If settings.url is missing, baseline warns at startup and canonicals fall back to relative paths.

Settings

The head module reads two parts of the settings argument. Full shape on Site settings.

Key Type Used for
settings.url string Building absolute canonicals.
settings.head object Site-wide head extras: link[], script[], meta[], style[]. Each entry is an object of attributes (plus optional content).

Options

Option Type Default Meaning
head.titleSeparator string ' – ' String placed between the page title and the site title in <title>.
head.showGenerator boolean false Emit <meta name="generator"> with Eleventy's version.

Per-page front matter

Set head: on a page to add or override entries. The shape mirrors settings.head:

---
title: 'About'
description: 'A short page-level description.'
head:
  link:
    - { rel: 'preload', as: 'font', href: '/assets/fonts/inter.woff2', crossorigin: '' }
  script:
    - { src: '/assets/js/about-only.js', defer: true }
  meta:
    - { name: 'theme-color', content: '#0c1a2c' }
  style:
    - { content: 'body { background: #fafafa; }' }
---

Page entries merge with site entries. Dedupe is applied per element type, last-wins on conflict (page entries land after site entries, so they win):

  • <meta> dedupes on whichever of charset, name, property, http-equiv is present (in that order).
  • <link> dedupes on the rel + hreflang + href triple. Different rel or different hreflang for the same href are kept as separate entries.
  • <script> and <style> are not deduplicated. If you list the same script twice, you get it twice.

Hreflang

When the multilang module is active and the page has a translationKey, the driver builds an <link rel="alternate" hreflang="..."> for every translation listed in the translation map. Nothing to configure on the head side. If the page has no translationKey, no alternates are emitted.


Tips

  • Keep <baseline-head> inside <head>, not <body>. The driver replaces the placeholder wherever it is, but the result is meaningful only inside <head>.
  • For absolute canonicals and any future absolute URLs, set settings.url. Relative canonicals are valid but limit some downstream tooling.
  • A page can opt out of head injection by setting _internal: true in its data. The page-context registry skips it, the driver finds no seeds, and the placeholder (if any) is left untouched.
  • Want a different renderer down the line? The driver is a single file in _baseline/modules/head/drivers/. The seam is internal today and not a user option, but the architecture is set up for swap.

Peer deps

PostHTML, bundled with Eleventy. capo.js, bundled with Baseline.

See also

Previous: assets

Next: multilang