Table of Contents
Page 7 of 10

Eleventy Excellent

Swap the hand-written head for <baseline-head>

base.njk loses its entire <head> block and gains the placeholder. Seven partials stop being included, and everything worth keeping is already in settings.head. One of the seven comes straight back as an inline block, under "What stays in the template".

Your site renders unstyled from here until the assets step. The stylesheet is wired through one of the partials you just stopped including, so this is expected rather than broken.

The dead partials stay on disk until the sweep at the end. That is deliberate, and it is what makes the sweep one legible pass rather than a footnote in five separate steps.

What Baseline replaces

Baseline composes the head from data, which is what the partials were doing by hand. Most of what you had moves across unchanged, and you pick up a handful of tags you were not emitting at all.

The new ones are charset, viewport, a composed <title>, twitter:card on every page, article:published_time and article:modified_time on posts, and the JSON-LD graph. Two more change rather than appear: robots arrives with a fuller set of directives than the googlebot tag it supersedes, and og:type becomes article on posts instead of website everywhere.

Three of these are worth checking against your own settings before you delete anything, since they carry values rather than just moving: the og:image URL has to be absolute, rel=author has to resolve from every page, and the locale has to be a real one.

View what moves, and what is dropped
tag outcome
og:image and its alt, width, height settings.seo.ogImage, and the URL must be absolute
color-scheme, format-detection, fediverse:creator settings.head.meta
rel=author, rel=me, both feeds, three icons, the manifest settings.head.link
theme-color ×2 stays in the template, see below
googlebot dropped, Baseline's robots is a superset
generator dropped, use options.head.showGenerator instead
article:author dropped, Baseline emits it and gates it correctly

Almost everything in that table is a move rather than a loss. The order to work in is the same anywhere: chain onto what your starter already declares, and reserve replacement for genuine overlap.

What stays in the template

Two theme-color tags cannot go into settings.head.meta. They differ only by media, and the cascade-time merge keys metas on name alone, so one is discarded before the head driver sees it.

Put them below the placeholder rather than inside it. Children of <baseline-head> are discarded; siblings survive, because the HTML parser folds meta, link, base, script, style and title back into the head element. That is the escape hatch for anything Baseline's dedupe would collapse.

Only the theme-color pair has to be there. The speculation-rules block below could equally live in settings.head.script, because a script entry accepts a content key that Baseline renders as the element's body. Keeping it in the template is a choice about where a block of JSON is easiest to read.

View the whole head region of `base.njk`
<baseline-head></baseline-head>
<meta name="theme-color" content="{{ meta.themeDark }}" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="{{ meta.themeLight }}" media="(prefers-color-scheme: light)" />

<!-- Prefetches likely next pages on link hover / interaction. Progressive enhancement:
  supported in chromium browsers, everyone else ignores the script block.
  https://developer.chrome.com/docs/web-platform/prerender-pages -->
<script type="speculationrules">
  {
    "prefetch": [
      {
        "where": {"href_matches": "/*"},
        "eagerness": "moderate"
      }
    ]
  }
</script>

{# Per-post share card. Emitted here because it cannot come from data:
    posts.11tydata.js declines Baseline's with `ogImage: false`. #}
{% if 'posts' in (tags or []) and slug %}
  <meta property="og:image" content="{{ ('/assets/og-images/' + slug + '-preview.jpeg') | url | absoluteUrl(settings.url) }}" />
  <meta property="og:image:alt" content="{{ title }}" />
  <meta property="og:image:width" content="1200" />
  <meta property="og:image:height" content="630" />
{% endif %}

Three separate things, all of them siblings: the pair Baseline's dedupe would collapse, a block that could live in settings and does not, and a workaround.

The share card at the bottom is the workaround, and it is there for a different reason from the other two.

Why the share card is in there

A per-post og:image cannot come from data. Baseline's SEO graph is built before eleventyComputed resolves, so a computed ogImage never reaches it and every post falls back to the site default without saying so. Decline Baseline's card and emit your own from the layout, which is what those four tags do.

// src/content/posts/posts.11tydata.js, in its final form
export default {
  layout: 'post',
  tags: 'posts',
  permalink: '/blog/{{ title | slugify }}/index.html',
  type: 'article',
  articleType: 'BlogPosting',
  ogImage: false
};

false, not an empty string. type and articleType are unrelated to the workaround: they tell Baseline these pages are articles, which is what gates og:type and article:published_time.

This is a workaround rather than a pattern.