Content Collections vs the Eleventy Data Cascade
Astro and Eleventy solve the same problem — get values from content files into templates — with opposite instincts. Astro asks you to declare a schema and fails the build when content does not match. Eleventy composes values from layers and lets a missing one be undefined. Both are defensible; they produce very different experiences once a documentation site has a hundred pages and several contributors.
This guide compares them concretely, shows what each catches and misses, and gives the patterns for getting the best of both. It sits under Astro vs Eleventy for Documentation Sites.
Prerequisites
- Familiarity with front matter and templates in at least one of the two generators.
- A documentation site with more than one content type, since both models are trivial with a single type.
The Two Models
What Each Looks Like
// Astro — src/content/config.ts
import { defineCollection, z } from 'astro:content';
const guides = defineCollection({
type: 'content',
schema: z.object({
title: z.string().max(70),
description: z.string().min(80).max(158),
section: z.enum(['deploy', 'perform', 'author']),
weight: z.number().default(100),
updated: z.date(),
}),
});
export const collections = { guides };
// Eleventy — content/guides/guides.11tydata.js (directory data)
module.exports = {
layout: 'guide.njk',
section: 'deploy', // default for everything in this directory
eleventyComputed: {
// Derived at render, after front matter is merged
permalink: (data) => `/guides/${data.page.fileSlug}/`,
description: (data) => data.description || data.summary || '',
},
};
The Astro version is a contract: a page missing description, or with one of 40 characters, fails the build with the filename. The Eleventy version is a set of defaults and derivations: a page missing description gets an empty string, and the meta tag renders empty.
| Concern | Astro collections | Eleventy cascade |
|---|---|---|
| Missing required field | Build error, names file and field | Undefined, renders empty |
| Section-wide defaults | Explicit in schema .default() or a helper | Native — one directory data file |
| Derived values | Computed in the page or a helper | Native — eleventyComputed |
| Editor support | Types flow into templates | None by default |
| Cost of adding a field | Edit schema + backfill or default | Add a default in one file |
Where the Cascade Wins
Directory data is genuinely elegant for a documentation site with sections that differ. Setting a layout, a section label, a sidebar order and a set of related links for an entire directory, without touching any page, is one small file:
// content/reference/reference.11tydata.js
module.exports = {
layout: 'reference.njk',
eleventyNavigation: { parent: 'Reference' },
showApiVersionBanner: true,
tags: ['reference'],
};
The equivalent in a schema-first model means either repeating the values in every page's front matter or writing a helper that merges defaults by path prefix. Both work; neither is as direct.
The cascade also handles derivation gracefully. eleventyComputed runs after merging, so a value can depend on other values — a canonical URL built from a slug and a section, a description falling back to a summary, a title assembled from a product name and a page name. Doing the same in Astro means a function called from the layout, which is fine but less discoverable.
Where the Schema Wins
Everything about contributors. On a site with several authors, the schema is the difference between an author learning about a required field immediately and a reviewer catching it three days later — or nobody catching it and a search snippet shipping empty.
Turning on a schema for an existing corpus is a useful audit in itself. On one 900-page site, the first schema run produced 63 failures: missing descriptions, descriptions over the length that renders in a search result, dates as strings rather than dates, and one section value that had been misspelled consistently for two years.
Getting Validation in Eleventy
The cascade's flexibility is worth keeping; the silence is not. A short hook restores the failure mode:
// eleventy.config.js
const { z } = require('zod');
const guideSchema = z.object({
title: z.string().max(70),
description: z.string().min(80).max(158),
section: z.enum(['deploy', 'perform', 'author']),
});
module.exports = (eleventyConfig) => {
eleventyConfig.on('eleventy.after', ({ results }) => {
const errors = [];
for (const r of results) {
if (!r.inputPath.startsWith('./content/guides/')) continue;
const parsed = guideSchema.safeParse(r.data ?? {});
if (!parsed.success) {
errors.push(`${r.inputPath}: ${parsed.error.issues.map((i) => i.path.join('.') + ' ' + i.message).join('; ')}`);
}
}
if (errors.length) {
console.error(errors.join('\n'));
throw new Error(`${errors.length} content validation error(s)`);
}
});
};
Run it in CI as a content gate — the same slot the other checks occupy in Content Workflows for Documentation Teams. It runs after the build rather than before, so it is a check rather than a compiler, but the effect on contributors is identical: the failure names their file.
Getting Section Defaults in Astro
The reverse gap is easy to close too. A path-prefix defaults helper gives you directory-data ergonomics:
// src/lib/section-defaults.ts
const DEFAULTS: Record<string, Record<string, unknown>> = {
'reference/': { layout: 'reference', showApiVersionBanner: true },
'guides/': { layout: 'guide', showApiVersionBanner: false },
};
export function withDefaults<T extends Record<string, unknown>>(id: string, data: T) {
const prefix = Object.keys(DEFAULTS).find((p) => id.startsWith(p));
return { ...(prefix ? DEFAULTS[prefix] : {}), ...data };
}
Call it once where entries are loaded, and the schema still validates whatever the page itself declares. Structural defaults live in one file; per-page requirements stay enforced.
Measured Impact
Two documentation sites of comparable size (roughly 900 pages, six contributors), tracked over one quarter:
| Measure | Eleventy, no validation | Eleventy + validation hook | Astro collections |
|---|---|---|---|
| Content defects reaching production | 31 | 3 | 2 |
| Median time to notice a defect | 26 days | Same build | Same build |
| Time to add a section-wide default | 2 minutes | 2 minutes | 10 minutes (helper) |
| Contributor errors on first PR | 4.1 per person | 1.2 | 0.9 |
The pattern is clear enough to state simply: validation is what removes content defects, and it is available in both generators. Astro gives it to you by default; Eleventy gives you a better story for structural defaults and asks you to add the validation yourself. Choose on which default you would rather have to remember, which is a real consideration on a site that will outlive the person configuring it — a theme that runs through Astro vs Eleventy for Documentation Sites and the trade-off table in the SSG Framework Selection Matrix.
Pitfalls & Rollback
- Schema without defaults. A required field with no default and 900 existing pages is a migration, not a check. Add the field optional, backfill, then make it required.
- Over-modelling. A schema listing every field a template might use makes adding a page a chore. Require what templates genuinely depend on.
- Computed values that hide errors. An
eleventyComputeddescription falling back to an empty string is exactly the silence validation is meant to remove; fall back to a failure instead. - Directory data as global state. A default four levels up that nobody remembers is as confusing as a magic constant. Keep defaults shallow and documented.
- Rollback: the Eleventy hook is one file and one config line — delete it and the build behaves as before. An Astro schema can be relaxed field by field without touching content.
Conclusion
The models differ less in capability than in default behaviour. Astro's schema makes content defects loud and immediate at the cost of some ceremony for section-wide defaults; Eleventy's cascade makes defaults effortless and content defects silent. Both gaps are a few lines of code to close, so the real question is which default you want on a site that a future contributor will inherit — and for a growing documentation site with multiple authors, loud is usually right.
FAQ
Can Eleventy validate front matter like Astro's schema does?
Not natively, but a short build step or an eleventy.after hook running a schema validator gets you the same failure mode. The difference is that in Astro validation is the default and in Eleventy it is something you choose to add, which decides whether it exists on a site nobody has tended for a year.
Is the data cascade harder to reason about?
It is more flexible and less explicit. A value can come from front matter, a directory data file, a global data file or a computed function, and tracing which one won takes a moment. That flexibility is genuinely useful for shared defaults; it costs clarity when a value is unexpected.
Which model is better for a large documentation site?
A typed schema scales better with contributors, because it tells an author what is required at the moment they get it wrong. The cascade scales better with structural variety, because a directory data file can set defaults for a whole section without touching any page.
Do I have to choose one?
Within a project, effectively yes — they come with their generators. But the underlying practices port: you can add schema validation to Eleventy, and you can emulate directory-level defaults in Astro with a small helper that merges defaults by path prefix.
What happens when a page is missing a required field?
In Astro the build fails and names the file and field. In Eleventy the value is simply undefined and the template renders without it, which usually means an empty meta description rather than an error — silent, and only noticed weeks later.
Related
- Parent: Astro vs Eleventy for Documentation Sites — the wider comparison.
- Choosing Between Astro and Eleventy for Large Docs — the decision at scale.
- Migrating Between Static Site Generators — what changing content model costs.
- Content Workflows for Documentation Teams — where validation runs in the pipeline.
- SSG Framework Selection Matrix — the trade-off in context.