EnglishNederlandsFrançais Toggle theme

Eleventy Baseline

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

Build a simple Baseline site

The shortest path from an empty folder to a working Baseline site. By the end you'll have a single page rendering through Eleventy, a tiny stylesheet bundled automatically, and a sitemap that updates itself. This is the tutorial every other tutorial builds on.

What you will build

  • An Eleventy site with one page.
  • Baseline wired with default options: multilingual off, sitemap on, navigator on in dev and off in production.
  • A small stylesheet flowing through Baseline's assets pipeline, and an auto-generated sitemap.xml next to it.

Prerequisites

  • Node 20.15.0 (or >=20).
  • npm.
  • A terminal.

1) Create the project

mkdir simple-baseline-site
cd simple-baseline-site
npm init -y

Open package.json and set "type": "module" (Baseline expects ESM):

{
	"name": "simple-baseline-site",
	"type": "module",
	"scripts": {
		"start": "rimraf dist/ && npx @11ty/eleventy --serve",
		"build": "rimraf dist/ && cross-env ELEVENTY_ENV=production npx @11ty/eleventy"
	}
}

Those two scripts are the only commands you need. From here on, npm start is local development and npm run build is the production build.

2) Install required packages

npm install @11ty/eleventy @11ty/eleventy-img # Eleventy and 11ty Image
npm install rimraf cross-env # Helper packages
npm install @apleasantview/eleventy-plugin-baseline # Finally, install Baseline

3) Configure Eleventy to use Baseline

Create eleventy.config.js in the project root:

import baseline, { config as baselineConfig } from '@apleasantview/eleventy-plugin-baseline';
import settings from './src/_data/settings.js';

/** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
export default async function (eleventyConfig) {
	eleventyConfig.addPlugin(baseline(settings));
}

export const config = baselineConfig;

Two things are worth a sentence:

  • baseline(settings, options) takes two arguments. settings is your site's identity (title, languages, head extras); options is runtime behaviour. Passing settings only leaves every option at its default, which is exactly what you want here.
  • export const config = baselineConfig; re-exports Baseline's directory contract, so Eleventy knows where to read input from and where to write output to. The config export reference explains why it has to be exported separately rather than set from inside the plugin.

Baseline wires Eleventy's HtmlBasePlugin to process.env.URL for you, which is what keeps canonical URLs and the sitemap pointing at the right origin.

Create a local .env for development, and set URL in your hosting environment for production. Baseline loads dotenv itself, so process.env.URL is available in both _data/settings.js and eleventy.config.js:

ELEVENTY_ENV="development"
URL="http://localhost:8080/"

4) Required site data

Create src/_data/settings.js. This is the canonical home for everything that identifies the site: title, production URL, indexing preferences, and the head extras (the <link>, <script>, and <meta> tags Baseline injects into every page).

export default {
	title: 'Simple Baseline Site',
	tagline: 'Hello, Eleventy + Baseline',
	url: process.env.URL || 'http://localhost:8080/',
	defaultLanguage: 'en',
	noindex: false,

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

The head.link[], head.script[], and head.meta[] arrays are how you add to the head from settings. Anything you put here lands on every page, alongside the defaults Baseline already produces (charset, viewport, title, description, canonical, robots). One stylesheet link, one script tag, one colour-scheme hint is enough to get started.

5) Minimal layout

Create src/_includes/layouts/base.njk. Notice <baseline-head></baseline-head>: that is the placeholder. Baseline replaces it with a full <head> element at build time, so your layout never writes one of its own. It sits as a sibling of <body>, not nested inside another head.

<!DOCTYPE html>
<html lang="{{ lang | default(settings.defaultLanguage) }}">
  <baseline-head></baseline-head>
  <body>
    <main id="main">
      <h1>{{ title }}</h1>
      {{ content | safe }}
      {% if page.url !== "/" %}
        <p><a id="go-back" href=""><span style="vertical-align: text-bottom;">&#8592;</span>&nbsp;Go back</a></p>
      {% endif %}
    </main>
    <script>
    const backBtn = document.getElementById("go-back");
    if ( backBtn ) {
      backBtn.addEventListener("click", (event) => {
        event.preventDefault();
        history.back();
      });
    }
    </script>
  </body>
</html>

6) Define your content directory

Eleventy uses directory data files to set repeating templating data, including permalinks for content files. We'll create one for our tutorial site. Inside src, create the content directory and its data file: src/content/pages/pages.11tydata.js.

A convention in Baseline is to use the slug front matter field and generate permalinks from them. Setting the permalink field is reserved for the homepage ("/") and whenever control is warranted.

export default {
	layout: 'layouts/base.njk',

	// Ensure we have a value to work wih by explicitly asking for the slug field.
	permalink: function ({ slug, page }) {
		if (!slug) {
			console.warn(`Warning: No slug found for ${page.inputPath}`);
			return false;
		}

		try {
			const normalizeSlug = this.slugify(slug);
			return `${normalizeSlug}/`;
		} catch (error) {
			console.error(`Error generating permalink for ${page.inputPath}:`, error);
			return false;
		}
	}
};

It would be tempting to use eleventyCompute for creating the permalink but this would always take precedence over the front matter field if set. The permalink function does plenty, including still allowing us to overwrite the field if needed.

7) Add a first page

Create src/content/pages/index.md:

---
title: 'Hello Baseline'
slug: 'simple-baseline-site'
description: 'A minimal Eleventy page powered by eleventy-plugin-baseline.'
permalink: '/'
layout: 'layouts/base.njk'
---

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

## Index

<ul class="index-links">
{% for item in collections.all %}
<li><a href="{{ item.url }}">{{ item.data.title }}</a></li>
{% endfor %}
</ul>

8) Minimal assets

Create src/assets/assets.11tydata.js to keep assets out of collections:

export default {
	eleventyExcludeFromCollections: true
};

Create src/assets/css/index.css:

body {
	font-family: system-ui, sans-serif;
	margin: 0;
	padding: 2rem;
	line-height: 1.5;
	color: #1f2937;
	background: #f8fafc;
}

h1 {
	margin-bottom: 0.5rem;
}

Create src/assets/js/index.js:

document.addEventListener('DOMContentLoaded', () => {
	console.log('Howdy!');
});

9) Run the site locally

npm start
  • Open the printed local URL (default http://localhost:8080/). You should see the page title rendered as a heading.
  • While dev runs, Eleventy also writes to dist/, so you can peek at the generated files, including sitemap.xml.

Eleventy never cleans dist/ for you. If a rebuild looks stale, stop the server and run npm start again.

10) Production build and inspect output

Change URL in .env to an absolute URL. On a deployed site that's your real production URL; for this exercise, any absolute URL (e.g. https://www.example.com/) works to verify the output. Then run:

npm run build
  • Find the built files in dist/.
  • dist/sitemap.xml is generated automatically because the sitemap module is on by default.
  • Reset URL in .env to "http://localhost:8080/".

Next steps

If you are looking to build a multilingual site, it's next up.

  • Add more pages under src/content/pages/ with front matter (title, description, permalink, layout).
  • If you deploy under a subpath, set pathPrefix in eleventy.config.js.
  • Once you have a production URL, update src/_data/settings.js so canonical URLs and the sitemap match.
  • The natural next chapter is the assets pipeline tutorial, which adds PostCSS and esbuild entries. After that, the head and noindex tutorial covers what Baseline injects into every page and how to extend it.