---
title: 'Build a simple site'
description: 'Create a single-language Eleventy site with Baseline defaults, head extras driven by settings, minimal CSS/JS, and an auto sitemap.'
slug: 'simple-baseline-site'
type: 'article'
date: 2026-01-25T00:00:00.000Z
lang: 'en'
url: 'https://www.eleventy-baseline.dev/docs/tutorial/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

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

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

```json
{
	"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

```bash
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:

```js
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) {
	await 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 | 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`:

```txt
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).

```js
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.

{% raw %}

```nunjucks
<!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>
```

{% endraw %}

---

## 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.

```js
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;
		}
	}
};
```

{% alertBlock %}

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.

{% endalertBlock %}

---

## 7) Add a first page

Create `src/content/pages/index.md`:

{% raw %}

```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>
```

{% endraw %}

---

## 8) Minimal assets

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

```js
export default {
	eleventyExcludeFromCollections: true
};
```

Create `src/assets/css/index.css`:

```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`:

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

---

## 9) Run the site locally

```bash
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`.

{% alertBlock "info" %}

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

{% endalertBlock %}

---

## 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:

```bash
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 | assets pipeline tutorial]], which adds PostCSS and esbuild entries. After that, the [[head-and-noindex | head and noindex tutorial]] covers what Baseline injects into every page and how to extend it.
