Table of Contents
Page 6 of 10

Eleventy Excellent

Add a schema.js file

One file in src/_data/, and no wiring. Dropping it in is the wiring, because schema is a cascade key.

// src/_data/schema.js
import settings from './settings.js';
import * as meta from './meta.js';

// Swap which one is null to model the site as a person instead.
const isPerson = meta.siteType === 'Person';

export default {
  organization: isPerson
    ? null
    : {'@type': 'Organization', name: settings.title, url: settings.url},

  person: isPerson
    ? {
        '@type': 'Person',
        name: meta.author.name,
        url: settings.url,
        // No origin, no image. undefined is stripped before output.
        image: settings.url ? new URL(meta.author.avatar, settings.url).href : undefined
      }
    : null,

  pieces: []
};

Those two imports are why settings had to be a data file: a file in src/_data/ can import a sibling, but not eleventy.config.js.

organization and person are reserved. Baseline builds the spine nodes and wires the @ids between them, and null means not set rather than empty, so it is stripped. pieces takes raw schema.org nodes and passes them through untouched, which is where anything Baseline does not model goes.

Nothing in the output changes yet. Baseline emits the JSON-LD from inside <baseline-head>, and the placeholder does not reach your layout until the head step, two on from here. The identity is ready and waiting when it does.

Generate the OG images into dist/

The share cards move out of the source tree and into build output. Six changes land together because none of them work alone.

The starter writes JPEGs into src/assets/og-images/ and lets passthrough copy them across. At eleventy.after passthrough has already run, so a card generated during a build only appears on the next one. That is why the generator is guarded to development and fourteen JPEGs are committed to the repository. Writing straight to dist/ dissolves the arrangement: the guard, the passthrough entry, the clean:og script and the committed output all go.

It is not free. The target now sits inside a directory clean wipes every build, so every card regenerates every time. That is Sharp work per post, traded for no generated artefacts in source.

Two bugs surface the moment output moves. Both are harmless only while nothing consumes the cards until the following build.

View the three edits

The output directory, in _config/events/svg-to-jpeg.js:

const ogImagesDir = './dist/assets/og-images';

The loop, in the same file. forEach discards the promise it is handed, so eleventy.after returns before any card has finished encoding:

const files = await fsPromises.readdir(socialPreviewImagesDir);
for (const filename of files) {
  // ...
}

And the permalink, in src/content/common/og-images.njk:

const permalink = '/assets/og-images/{{ post.data.slug | slugify }}-preview.svg';

That one is post.data.title | slugify in the starter, while the share-card tags build the og:image URL from slug. The two agree only while every title happens to slugify to its slug, and diverge the first time a title is edited. Fix it here, before the head step puts those tags in base.njk and gives the mismatch somewhere to show.

Both worked only because nothing read the cards until the next build.