Table of Contents
Page 3 of 6

Eleventy Base Blog

Move the directories under src/

mkdir -p src/assets
mv _data src/_data
mv _includes src/_includes
mv content src/content
mv css src/assets/css
mv public src/static

Plain mv is fine. Git infers the renames from content similarity when you stage them.

The on-disk folder is static/, while the virtual directory key Baseline registers is public. The config-export reference covers that split.

_config/ stays where it is. Nothing in Baseline's contract has an opinion about it.

The build is broken from here until you rewrite the config. dir still points at content/, ../_includes and ../_data, all of which just moved. Expected, and not worth debugging.

Write the settings file

Create src/_data/settings.js from metadata.js:

export default {
	title: 'Eleventy Base Blog v9',
	tagline: 'A blog built with Eleventy Baseline',
	description: 'I am writing about my experiences as a naval navel-gazer.',

	url: process.env.BASELINE_URL || 'http://localhost:8080',

	noindex: false,
	defaultLocale: 'en',

	head: {
		link: [{ rel: 'stylesheet', href: '/assets/css/index.css' }]
	},

	// Used by the feed and by templates.
	author: {
		name: 'Your Name Here',
		email: 'youremailaddress@example.com',
		url: 'https://example.com/about-me/'
	}
};

This has to exist before the config swap, because the config imports it. Living in src/_data/ also hands it to every template as settings.

url is the one setting Baseline warns about when it is missing. It anchors canonicals, sitemap entries and the structured-data graph, so it must be an absolute URL. The full shape is on site settings.

Leave metadata.js where it is. The feed config and every layout still read metadata.*. Retiring it comes last, once its consumers have moved.

Move standalone pages into pages/

mkdir -p src/content/pages
mv src/content/about.md src/content/pages/
mv src/content/404.md src/content/pages/

Standalone pages only. index.njk, blog.njk, tags.njk and tag-pages.njk stay where they are, along with blog/ and feed/.

The folder name is free, since Baseline derives URLs from slug rather than from path. pages/ matches the tutorial.

Leave the front matter alone for now. That is the next step, and it is the step that makes this one correct.

Without this, everything under src/content/ lands at /content/…, because the input directory is now src/.

Two directory data files, one rule each:

File Rule Result
content.11tydata.js slug/slug/ root pages, and pages/ by cascade
content/blog/blog.11tydata.js slug/blog/slug/ posts keep their prefix

Create src/content/content.11tydata.js:

export default {
	layout: 'layouts/base.njk',
	permalink: function ({ slug, page }) {
		if (!slug) {
			console.warn(`Warning: No slug found for ${page.inputPath}`);
			return false;
		}

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

Posts want a segment of their own, so src/content/blog/blog.11tydata.js overrides it with the same shape and a different return:

export default {
	tags: ['posts'],
	layout: 'layouts/post.njk',
	permalink: function ({ slug, page }) {
		if (!slug) {
			console.warn(`Warning: No slug found for ${page.inputPath}`);
			return false;
		}

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

Keep the guard. A missing slug otherwise gives you a page at an address you did not choose, on a build that exits zero.

Two files rather than three, and layout at the top set to base.njk: both ride the same cascade, which content organisation covers. blog/ overrides layout and permalink, the homepage names layouts/home.njk itself so the message box stays on one page, and everything else inherits.

Write slugs explicitly in front matter. A directory-data permalink function cannot read an eleventyComputed value, because computed data resolves after the cascade the function runs in. It gets undefined, warns, and returns false; the template's own permalink wins later, so the output looks right and only the console complains. The warning names the template rather than the data file that produced it.

Three exceptions:

  • index.njk: permalink: "/" and layout: "layouts/home.njk".

  • 404.md: permalink: 404.html.

  • tag-pages.njk: permalink as a plain front matter string, not a function and not a computed value:

    const permalink = "/tags/{{ tag | slugify }}/";

    Plain front matter wins the cascade merge outright, so the directory function is never called. Eleventy renders permalink strings as templates, which is what makes this work.

Add a title to index.njk, blog.njk, tags.njk, about.md and 404.md in the same pass. None of them has one. It does not matter today, and it matters when you swap the head: the head module builds <title>, the Open Graph title and the JSON-LD node from title, and the layout's Eleventy Base Blog fallback disappears along with the old head.

Every file is now where Baseline expects it, and the build is still broken, because the config is still describing the old layout. The next page fixes that by throwing it away.