Table of Contents

Quickstart

The minimum working setup. A checklist; for a guided walk with reasoning, see the simple site tutorial.


Prerequisites

Node 22 or newer, and npm. Eleventy 3.x is what Baseline is tested against; a stable 4.x is permitted and untried.

A package.json with "type": "module" and Eleventy scripts:

{
	"name": "baseline-quickstart",
	"type": "module",
	"scripts": {
		"start": "npm-run-all clean dev",
		"dev": "eleventy --serve",
		"build": "npm-run-all clean build:eleventy",
		"build:eleventy": "cross-env ELEVENTY_ENV=production eleventy",
		"clean": "rimraf dist/"
	}
}

No .env file. ELEVENTY_ENV is set by the build script above, and BASELINE_URL comes from your host in production, falling back to localhost while you work. Nothing in Baseline loads a .env, so keeping one means adding a loader for no gain. The variable name is yours to pick, though deployment checks names two that are already taken.


Install

mkdir baseline-quickstart
cd baseline-quickstart
npm init -y
npm install @11ty/eleventy @apleasantview/eleventy-plugin-baseline
npm install rimraf cross-env npm-run-all # the helpers the scripts above use

Install warnings

A prerelease Eleventy is refused. Semver excludes prereleases from a range that carries none, so 4.0.0-alpha.10 fails where 4.0.0 passes, and npm's error prints both values without saying why. Pin to a stable release, or install with --legacy-peer-deps.

esbuild fetches a platform binary in a postinstall script. Run with ignore-scripts and the JavaScript half of the assets pipeline is dead, with nothing on screen to say so. sharp, via @11ty/eleventy-img, is the same.


Configure

Create eleventy.config.js at the project root:

import baseline, { config as baselineConfig } from '@apleasantview/eleventy-plugin-baseline';

const settings = {
	title: 'My Site',
	url: process.env.BASELINE_URL || 'http://localhost:8080/',
	defaultLanguage: 'en',

	head: {
		link: [{ rel: 'stylesheet', href: '/assets/css/index.css' }],
		script: [{ src: '/assets/js/index.js', defer: true }],
		meta: [{ name: 'color-scheme', content: 'light dark' }]
	},

	seo: {
		ogImage: {
			url: 'https://www.example.com/og.jpg',
			width: 1200,
			height: 630,
			alt: 'My Site'
		}
	}
};

export default async function (eleventyConfig) {
	await eleventyConfig.addPlugin(
		baseline(settings, {
			head: { showGenerator: true }
		})
	);

	eleventyConfig.addGlobalData('settings', settings);
}

export const config = baselineConfig;

Five things to notice:

baseline() takes two arguments. settings is the site identity (title, url, languages, head extras, seo defaults); options is runtime behaviour (which modules are on, log verbosity, per-module options). Key by key, that is site settings and the plugin entrypoint; why the two are apart is on how Baseline works.

addGlobalData('settings', settings) is what hands the same object to your templates, so a layout can read settings.title without importing anything. Putting the object in src/_data/settings.js and importing it here does that for free, and is the better shape once the file grows. One file is the simpler start.

head is how tags reach every page. Baseline compiles the CSS and JS entry points below, but it does not link them for you, so a bundle missing from this list gets built and never loaded. The head module lists what Baseline emits alongside your entries. seo sets site-wide social and schema defaults that any page can override in front matter; ogImage is absolute on purpose, because it ends up in JSON-LD where Eleventy's HtmlBasePlugin cannot reach it to expand a relative path.

showGenerator is off by default and turned on here. It adds a <meta name="generator"> carrying Eleventy's own version string, which is how anyone counting Eleventy sites in the wild finds yours. A small courtesy to the project. Turn it off if you would rather not say.

The config re-export looks unusual. It is. That way both Baseline and Eleventy agree on where things live (src/ as input, dist/ as output, the asset and public folders). Why a plugin cannot set those for you is on how Baseline works; the contract it ships is the config-export reference's.


Project files

Create the rest of the project files:

src/_includes/layouts/base.njk: base layout. Drop <baseline-head></baseline-head> in place of the <head> element. That placeholder is the one element you write yourself.

src/content/index.md: a starter page with front matter and a body. The permalink is what puts it at the site root; without one, Eleventy derives the URL from the file's path and it lands at /content/ instead.

---
title: 'Hello Baseline'
description: 'A minimal Eleventy page powered by eleventy-plugin-baseline.'
permalink: '/'
---

You are looking at a page rendered with Eleventy and the Baseline plugin defaults.

src/assets/assets.11tydata.js, src/assets/css/index.css, src/assets/js/index.js: asset entry points (one CSS, one JS).

src/static/: passthrough-copied to the site root. The on-disk folder is static/; the virtual directory key Baseline registers is public.


Run and build

Development:

npm start

Open http://localhost:8080/. Check dist/ for output, including dist/sitemap.xml.

Production build. BASELINE_URL is the address the build claims as its own, so supply the real one for the command. Any absolute URL does the job for this exercise:

BASELINE_URL=https://www.example.com/ npm run build

The NAME=value prefix works in bash and zsh; on Windows cmd or PowerShell, put it in the script through cross-env instead. On a real deploy your host sets the variable and you never type it.

Inspect dist/ for the final files, and check that the canonical link and dist/sitemap.xml both carry the URL you passed rather than localhost.

That fallback in the settings file is a convenience with a sharp edge: a production build that never sees BASELINE_URL will not warn, it will quietly ship http://localhost:8080/ in every canonical, og:url and JSON-LD node. Set it on the host before the first deploy, not after.

The drafts preprocessor (a build-time filter that drops templates marked draft: true) is on automatically. Drafts render while the dev server is running (npm start) and are dropped from a build (npm run build). The switch is Eleventy's own run mode rather than ELEVENTY_ENV, so any build drops them, production or not. The two variables are put side by side on project structure.


Next steps