Eleventy Excellent
Wire in Baseline
Baseline arrives as one addPlugin call, and the settings file arrives with it.
import baseline, {config as baselineConfig} from '@apleasantview/eleventy-plugin-baseline';
import settings from './src/_data/settings.js';
export default async function (eleventyConfig) {
await eleventyConfig.addPlugin(baseline(settings));
// ...
}
Write src/_data/settings.js in full now rather than starting with an inline object. Three later steps add keys to it, and the schema step cannot read it at all while it is a const in the config, because a data file cannot import from eleventy.config.js. A file in src/_data/ is global data under its own name, so eleventyConfig.addGlobalData('settings', settings) is not needed either.
Give the file a default export and nothing else. A named export beside it hands templates the module namespace instead of the object, so settings.url is undefined everywhere, silently, on a green build.
View `src/_data/settings.js` in full
const siteUrl = process.env.BASELINE_URL || 'http://localhost:8080/';
const absolute = (path) => new URL(path, siteUrl).href;
export default {
title: 'Eleventy Excellent',
description: 'Eleventy starter for building modern, resilient websites',
url: siteUrl,
// meta.js says en_EN. Baseline derives og:locale from the locale, so give it
// the locale rather than the short code.
defaultLanguage: 'en',
defaultLocale: 'en-GB',
seo: {
// Absolute, because Baseline passes this through untouched.
ogImage: {
url: absolute('/assets/images/template/opengraph-default.jpg'),
width: 1200,
height: 630,
alt: 'An Eleventy starter with CUBE CSS, Every Layout and design tokens'
}
},
head: {
// theme-color is emitted from base.njk instead. See the head step.
meta: [
{name: 'color-scheme', content: 'light dark'},
{name: 'format-detection', content: 'telephone=no'},
{name: 'fediverse:creator', content: '@lene@front-end.social'}
],
link: [
// Empty string, not `true`: attributes pass through to the serialiser
// raw, so `crossorigin: true` would emit crossorigin="true".
{
rel: 'preload',
href: '/assets/fonts/redhat/red-hat-display-v7-latin-900.woff2',
as: 'font',
type: 'font/woff2',
crossorigin: ''
},
{rel: 'stylesheet', href: '/assets/css/global/index.css'},
{rel: 'stylesheet', href: '/assets/css/local/index.css'},
{rel: 'author', href: '/humans.txt'},
{rel: 'me', href: 'https://front-end.social/@lene'},
{rel: 'alternate', type: 'application/atom+xml', title: 'Atom Feed', href: '/feed.xml'},
{rel: 'alternate', type: 'application/json', title: 'JSON Feed', href: '/feed.json'},
{rel: 'icon', href: '/favicon.ico', sizes: 'any'},
{rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml'},
{rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png'},
{rel: 'manifest', href: '/site.webmanifest', crossorigin: 'use-credentials'}
],
script: [{src: '/assets/js/index.js', defer: true, module: true}]
}
};
Every entry in head.link and head.meta is a tag lifted out of a hand-written partial, which the head step deletes. The second font preload is cut here for length.
Two of those values are corrections rather than moves. rel: author is relative in the starter, so it 404s on every page but the home page, and defaultLocale is there because meta.js carries en_EN, which is not a locale.
settings.head also declares two stylesheets and a script that do not exist until the assets step. Nothing reads them until <baseline-head> arrives, so the exposure is one step: the head swap links assets that 404, which turns the unstyled window into a 404 window.
Delete the sitemap template in the same commit
Not before, and not after. The starter's sitemap.njk declares permalink: /sitemap.xml and Baseline registers a virtual template at the same path. Eleventy treats a duplicate output path as fatal, so deleting first leaves you with no sitemap, and wiring first leaves you with a build that fails.
This is the one deletion that cannot wait for the sweep at the end. On any site with its own sitemap template, it is also the first thing that happens when Baseline is registered: the build stops, and the error names a Baseline-internal template you have never seen. The image shortcode handles the same kind of clash by standing down and telling you in the log. The sitemap module has no equivalent check.
What the build says
[baseline] Baseline v0.1.0-next.46, running Eleventy v3.1.6
[baseline] Modules: sitemap, navigator, head, assets
[baseline] Already defined here, left alone: image
The third line is the one to read. Baseline registers its own image shortcode only where nothing has claimed the name, so yours keeps it. It behaves that way throughout: it takes a surface only if you have not already claimed it, and tells you when it leaves one alone.
Guard the image work against the pre-pass
Baseline renders your whole site once before the real build, in a second Eleventy instance in the same process, to construct the content graph. Every template runs for real, so anything expensive your config does happens twice.
BASELINE_PREPASS_ACTIVE is '1' while that runs, and it is set before the inner instance reads your config, so a registration-time guard works as well as a run-time one. Compare it to '1' explicitly: it is reset to the string '0' rather than deleted, so a bare truthiness check stays true for the rest of the process.
Three places want guarding, and none of them skips work outright. Two switch eleventy-img to statsOnly, because the pre-pass still has to resolve the metadata its markup is built from.
View the three guards
// eleventy.config.js, skipping the transform registration entirely
if (process.env.BASELINE_PREPASS_ACTIVE !== '1') {
eleventyConfig.addPlugin(plugins.eleventyImageTransformPlugin, {
// ...
});
}
// _config/shortcodes/image.js and _config/events/svg-to-jpeg.js
statsOnly: process.env.BASELINE_PREPASS_ACTIVE === '1',
The asset compile is the one you cannot guard. Templates inline its output, and the pre-pass renders those templates for real, so skipping it fails the pre-pass with template not found. The rule: guard work whose product the pre-pass does not need, and never guard work its render depends on.
Check the numbers rather than trusting them. Fresh encodes are what matters, and with all three guards in place they come to 46 either side of Baseline.
154 optimized (108 cached) no guards
133 optimized (87 cached) + transform skipped
95 optimized (49 cached) + shortcode and svgToJpeg
The totals stay above the pre-Baseline figure because the pre-pass still looks images up. Those are metadata reads rather than encodes.
Swap in Baseline's config export
Spread Baseline's config rather than re-exporting it.
export const config = {
...baselineConfig,
markdownTemplateEngine: 'njk',
dir: {...baselineConfig.dir, layouts: '_layouts'}
};
The directory conventions you adopted by hand now come from the plugin, and they agree because you made them agree first. One override earns its place: Baseline's contract has no layouts key, so a wholesale re-export moves layout resolution into _includes/, which means moving every layout and rewriting every reference to one. Keeping the directory is one line, and spreading is what makes keeping it possible.