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

Schema validation versus layered cascade Astro content collections take front matter through a declared schema, which either produces typed data or fails the build naming the file and field. The Eleventy data cascade merges global data, directory data, front matter and computed data in priority order, producing a value that may be undefined without any error. Declare and verify, or layer and merge Astro content collections front matter schema typed data → templates build fails, names the file and field Eleventy data cascade global _data directory data front matter computed data merge by priority value or undefined no error either way template renders anyway The difference that matters in practice is the red box: one model has it, the other does not
Both models get data into templates. Only one of them tells you, at build time and by filename, that a page is missing something a template depends on.

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.

ConcernAstro collectionsEleventy cascade
Missing required fieldBuild error, names file and fieldUndefined, renders empty
Section-wide defaultsExplicit in schema .default() or a helperNative — one directory data file
Derived valuesComputed in the page or a helperNative — eleventyComputed
Editor supportTypes flow into templatesNone by default
Cost of adding a fieldEdit schema + backfill or defaultAdd 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.

Defects found by turning on a schema A breakdown of 63 content defects found on first running a schema over a 900-page site: 28 missing or empty descriptions, 19 descriptions outside the useful length range, 9 dates stored as strings, 5 pages with an invalid section value and 2 titles over 70 characters. 63 defects, found in one build, on a site nobody thought was broken Missing description 28 Wrong length 19 Date as string 9 Invalid section / title 7 Shared linear scale · every one of these rendered without an error before the schema existed
None of these produced a broken page. They produced empty meta tags, truncated snippets and one section that had quietly never appeared in its own index.
Closing the gap in each direction Two rows. Eleventy starts with native directory defaults and no validation, and adding a validation hook gives it both. Astro starts with native schema validation and no directory defaults, and adding a path-prefix helper gives it both. Each gap is a small amount of code. Each generator is one small file away from the other's strength Eleventy directory defaults native validation missing add an after hook ~25 lines Astro schema validation native directory defaults missing path-prefix helper ~12 lines Choose on which behaviour you want by default, not on which is possible — both are possible in both
The defaults matter more than the ceiling: on a long-lived site, the behaviour nobody has to remember to configure is the one you actually get.

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:

MeasureEleventy, no validationEleventy + validation hookAstro collections
Content defects reaching production3132
Median time to notice a defect26 daysSame buildSame build
Time to add a section-wide default2 minutes2 minutes10 minutes (helper)
Contributor errors on first PR4.1 per person1.20.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 eleventyComputed description 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.