Add a layout and a stylesheet
Your pages render, and they arrive with nothing around them: no <head>, no navigation, nothing to look at. This page gives them a shell. It is also where two things you set up earlier finally reach a file.
Write the layout
One line in here is going to look wrong. Leave it alone, it is the point of the page. Create src/_includes/layouts/base.njk:
<!DOCTYPE html>
<html lang="{{ lang or settings.defaultLanguage }}">
<baseline-head></baseline-head>
<body>
<header>
<a href="/">{{ settings.title }}</a>
<p>{{ settings.tagline }}</p>
</header>
<main id="main">
<h1>{{ title }}</h1>
{{ content | safe }}
</main>
<footer>
<p>Proudly powered by Eleventy and Baseline.</p>
</footer>
</body>
</html>
<baseline-head></baseline-head> is not a tag that goes inside the head. It is the head. Baseline replaces the whole element at build time with a real <head>, assembled from your front matter, your settings file and its own defaults. That is why it sits as a sibling of <body> rather than wrapping anything, and why you never write a <title> yourself. If you catch yourself hand-writing a head tag, something has gone sideways. The head module lists everything it emits.
Header, main, footer. Three siblings, no wrapper, which is as much structure as this site needs. The header reads settings.title and settings.tagline straight out of the settings file from two pages ago. You did not import it. Living in src/_data/ is what does that.
The lang attribute reads the page's own lang and falls back to settings.defaultLanguage. On a single-language site those are the same value and the fallback is the one doing the work. It matters later: the multilingual tutorial gives each language folder its own lang, and a layout that only ever read defaultLanguage would label every Dutch page as English.
{{ content | safe }} is where each page's rendered Markdown lands. safe tells Nunjucks it is already HTML and should not be escaped.
Point the pages at it
The layout exists and nothing is using it. One key fixes that, in one file. Create src/content/content.11tydata.js:
export default {
layout: 'layouts/base.njk'
};
Directory data reaches down, not across. Sitting at the top of content/ means it reaches everything below: pages/, posts/, and the homepage at the root. One line, every page.
That is the difference between the two rules you now have. The layout is the same everywhere, so it is set once at the top. The address rule is not, which is why it lives in pages/ and posts/ separately and says something different in each. Anything a page disagrees with, it overrides in its own front matter.
Restart the dev server and look again. The words are the same, but there is a document around them now, and a title in the browser tab you did not write.
Add the stylesheet
Baseline compiles two entry points, one for CSS and one for JS, both named index. That sounds restrictive until you notice that @import and import still reach everything else from there. The assets module covers what the pipeline does with them.
First a small piece of housekeeping, before the files rather than after. Create src/assets/assets.11tydata.js:
export default {
eleventyExcludeFromCollections: true
};
Baseline registers CSS and JS as template formats so it can compile them, which technically makes them pages. Without this file they turn up in collections.all, which is the collection most listing templates reach for, and a stylesheet ends up in a list of things somebody might want to read. One line, one folder, problem gone.
Now the stylesheet, 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;
}
And src/assets/js/index.js, which the site does not need but the settings file has been promising since page three:
document.addEventListener('DOMContentLoaded', () => {
console.log('Howdy!');
});
Both compile into dist/assets/, mirroring the folders they came from, so src/assets/css/index.css is served at /assets/css/index.css. That is the path already sitting in settings.head.link, which is why the stylesheet loads without you adding a <link> anywhere. Compiling and loading are two separate jobs. Baseline only does the first.
Reload once more and the page has a face.
Put the about page on the homepage
One thing is missing from the shape: the homepage lists posts but says nothing about the site. Open src/content/index.md and add this above the ## Latest posts heading, so the site introduces itself before it lists anything:
{% set about = _navigator.nodes["/about/"] %}
<p>{{ about.excerpt }}</p>
<p><a href="{{ about.url }}">Read more</a></p>
Nothing new went into the about page to make this work. _navigator is a global Baseline registers, and nodes is every page on the site keyed by its URL. The excerpt on each one is that page's opening paragraph, read out of the rendered HTML rather than out of front matter, which is why you are not maintaining the sentence in two places.
That is the pre-pass, which the logs have been mentioning since page three, finally doing something you can see. Baseline builds the whole site once before it builds it for real, so that any page can know what is on any other page. Without it a template could only ever see itself.
This is also why the excerpt would have been empty an hour ago. Extraction reads the rendered document and looks for
<main>inside it, so before the layout existed there was nothing to extract from. The layout is what turned your pages into documents.
The "/about/" written into the template is the ugly part. It works, and it is honest about what it does, but a site with more pages would want to look the page up rather than name it. The globals reference has the rest of what is on _navigator.
The site is finished, in the sense that everything it needs now exists. What is left is knowing what happens when you run it for real.