Table of Contents
Page 3 of 10

Eleventy Excellent

Adopt Baseline's project structure

_config/ comes out of the input directory and the content directories go under src/content/, leaving src/ holding only what Eleventy treats specially. Almost all of this is renaming.

_config/            events, filters, plugins, setup, shortcodes, utils
src/
  _data/
  _includes/        head, partials, schemas, webc
  _layouts/
  assets/           css, fonts, images, scripts, svg, og-images
  content/          common, docs, pages, posts, tags
eleventy.config.js

src/common/ is the exception. Upstream it mixes the feeds, robots and the pa11y config with 404.md and the two tag templates. Split it, so that what stays behind is machine output and nothing else. Three directory-level keys land there later and they only work if nothing in the directory is a page someone visits.

View the three files that leave `common/`
src/common/404.md        ->  src/content/pages/404.md
src/common/tagList.njk   ->  src/content/tags/tagList.njk
src/common/tags.njk      ->  src/content/tags/tags.njk

Check every collection glob afterwards. getFilteredByGlob returns an empty array for a glob that matches nothing, and does not warn. Two of the starter's three break here. The first empties /blog/, its second page, the thirteen OG cards and the homepage post list, all while the build stays green. The second silently narrows the sitemap from 23 URLs to 21 by no longer reaching the tag templates, which is why common/ has to move under src/content/ in the same commit. The third survives because it walks getAll() rather than a path.

View the two globs in `_config/collections.js`
// getAllPosts, was './src/posts/**/*.md'
collection.getFilteredByGlob('./src/content/posts/**/*.md');
// showInSitemap, was './src/**/*.{md,njk}'
collection.getFilteredByGlob('./src/content/**/*.{md,njk}');

Globs are the ones that fail quietly, but they are not the only hard-coded paths. The rest are the _config/ imports at the top of eleventy.config.js, an ignores entry pointing at pa11y.njk, and Tailwind's content globs.

Normalise the Eleventy config

The starter returns its config from inside the callback. Eleventy documents a static named config export as the canonical shape, and adopting it now is what lets Baseline's own export drop in later.

export default async function (eleventyConfig) {
  // ...
}

export const config = {
  markdownTemplateEngine: 'njk',
  dir: {
    output: 'dist',
    input: 'src',
    includes: '_includes',
    layouts: '_layouts'
  }
};

Same values, moved out of the return. Nothing about the build changes.

Switch to JS data files and front matter

The two .json directory data files become .11tydata.js, and the three templates that paginate move to ---js front matter. That second one is an ordinary Eleventy feature rather than anything Baseline adds: open the fence with ---js instead of --- and the block is JavaScript, where each const becomes a front matter key. Pagination is the reason: it turns one file into many pages, and each of those pages inherits the same fileSlug, so a computed slug is the only way to give them separate identities.

View the five directory data files
// src/assets/assets.11tydata.js
export default {
  eleventyExcludeFromCollections: true
};
// src/content/common/common.11tydata.js
export default {};
// src/content/content.11tydata.js
export default {};
// src/content/docs/docs.11tydata.js
export default {
  tags: 'docs',
  permalink: false
};
// src/content/posts/posts.11tydata.js
export default {
  layout: 'post',
  tags: 'posts',
  permalink: '/blog/{{ title | slugify }}/index.html'
};
View the three front matter blocks in full

Every value the YAML held becomes a const of the same name. The only addition is eleventyComputed.slug.

---js
// src/content/common/og-images.njk
// Needed when generating locally: the font must be installed on your system.
const fontDisplay = "'Red Hat Display', Ubuntu";
const fontBody = "'Atkinson Hyperlegible', Ubuntu";

const background = '#FBBE25';
const text = '#161616';
const siteUrl = 'eleventy-excellent.netlify.app';

const pagination = {
  data: 'collections.allPosts',
  size: 1,
  alias: 'post'
};

const permalink = '/assets/og-images/{{ post.data.title | slugify }}-preview.svg';

const eleventyExcludeFromCollections = true;

const eleventyComputed = {
  // One SVG per post, all from this one file. A per-page slug keeps Baseline's
  // slug index from seeing thirteen pages claiming this file's slug.
  slug: (data) => `og-${data.post.data.page.fileSlug}`
};
---
---js
// src/content/pages/blog.njk
const layout = 'base';
const title = 'Blog';
const description = 'All blog posts can be found here';

const pagination = {
  data: 'collections.allPosts',
  size: 8
};

const permalink =
  'blog/{% if pagination.pageNumber >=1 %}page-{{ pagination.pageNumber + 1 }}/{% endif %}index.html';

const eleventyComputed = {
  // Mirrors the permalink: page one is /blog/, the rest are /blog/page-N/.
  slug: (data) =>
    data.pagination.pageNumber === 0 ? 'blog' : `blog-page-${data.pagination.pageNumber + 1}`
};
---
---js
// src/content/tags/tagList.njk
const layout = 'tags';

const pagination = {
  data: 'collections.tagList',
  size: 1,
  alias: 'tag'
};

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

const eleventyComputed = {
  title: '{{ meta.blog.tagSingle }}: {{ tag }}',

  // One input file, one page per tag. Without a per-page slug every one of
  // them inherits the file's, and Baseline's slug index throws on the second.
  slug: (data) => data.tag
};
---

Permalinks stay template strings inside a JS block, which is why the braces are still there. The og-images permalink is corrected later, in the step that moves the cards to dist/.