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:
multilingualoff,sitemapon,navigatoron in dev and off in production. - A small stylesheet flowing through Baseline's assets pipeline, and an auto-generated
sitemap.xmlnext 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.settingsis your site's identity (title, languages, head extras);optionsis 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;">←</span> 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
eleventyComputefor 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, includingsitemap.xml.
Eleventy never cleans
dist/for you. If a rebuild looks stale, stop the server and runnpm startagain.
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.xmlis generated automatically because the sitemap module is on by default.- Reset
URLin.envto "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
pathPrefixineleventy.config.js. - Once you have a production URL, update
src/_data/settings.jsso 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.
Previous: Tutorials