Enabling Incremental Builds in Eleventy

When an Eleventy site grows past a few hundred pages, the edit-save-refresh loop stops feeling instant. A full rebuild that takes four or five seconds is a tax you pay on every keystroke-driven save. Eleventy's --incremental flag fixes this by rebuilding only the templates affected by the file you just changed, so editing one Markdown post re-renders one page instead of the whole site. This guide covers the flags, the programmatic API, exactly what gets rebuilt, and the measured rebuild times. It is the Eleventy-specific piece of Incremental Builds and Build Caching for SSGs.

Prerequisites

  • Eleventy 2.0 or newer (the incremental engine improved substantially over 1.x). Check with npx @11ty/eleventy --version.
  • A site large enough that a full build is noticeably slow — incremental builds pay off above roughly 200 pages or when you do per-page image work.
  • Familiarity with running Eleventy in watch or serve mode, since that is where incremental builds do their work.

What "Incremental" Means in Eleventy

Eleventy tracks a dependency graph between your templates, layouts, includes, and data files. When you save a file in watch mode with --incremental, it walks that graph to find every template that depends on the changed file and rebuilds only those. A leaf Markdown file with no dependents rebuilds exactly one output page; a shared layout rebuilds every page that extends it — which is correct, not a bug.

The graph is built from the layout chain (layout: front matter and {% extends %}/{% include %} directives), the data cascade (a template's own front matter, directory data files, and global _data), and any files a template pulls in through eleventy.addWatchTarget or plugin hooks. Passthrough file copy is folded in too: with --incremental, only the passthrough assets that actually changed are re-copied instead of the whole addPassthroughCopy set. That is why a plain content edit stays cheap even on a site that ships hundreds of static assets alongside its templates.

The collections caveat

The one dependency Eleventy's incremental engine cannot always infer is a collection. If your homepage renders collections.post and you add or edit a post, the incremental build rebuilds the post you touched but may not rebuild the listing page that references the collection, because that data relationship is computed at build time rather than declared in the template. The practical rule: templates that render lists, tag pages, feeds, or sitemaps can go stale under --incremental. When you are specifically working on a listing page, edit that template (or restart the dev server) to force it through the graph. For everyday post-body editing — the case incremental builds exist for — this never bites.

Eleventy incremental rebuild dependency graph A dependency graph showing that editing a single post rebuilds only that post, while editing a shared layout rebuilds every page that inherits it. Affected nodes are highlighted in green, unaffected nodes are muted. What rebuilds when you change one file Edit a single post post-a.md ✎ post-a/index.html other 499 pages untouched · 1 page rebuilt Edit the base layout base.njk ✎ all posts all pages shared dependency · all 500 pages rebuilt
Eleventy rebuilds along its dependency graph: a leaf post rebuilds one page, but a shared layout is a dependency of every page that inherits it, so all of them rebuild.

The Recipe

Run Eleventy in serve mode with the incremental flag. In package.json:

{
  "scripts": {
    "dev": "eleventy --serve --incremental",
    "dev:fast": "eleventy --serve --incremental --ignore-initial",
    "build": "eleventy"
  }
}

--incremental enables per-change rebuilds. --ignore-initial goes one step further: it skips writing any files on the first pass when the server starts, so startup is near-instant on a large site, and you only pay to render the page you actually edit. This is the combination to reach for when you are working on a single template inside a 1,000-page site and do not want to wait for a full build just to open the dev server.

For the standalone CLI:

# Watch and serve, rebuilding only what changes
npx @11ty/eleventy --serve --incremental

# Skip the initial full write, then build on change only
npx @11ty/eleventy --serve --incremental --ignore-initial

Programmatic API

If you embed Eleventy in a larger Node process, the programmatic API runs the same incremental engine. Construct an instance and call watch():

import Eleventy from '@11ty/eleventy';

const eleventy = new Eleventy('./src', './dist', {
  configPath: './eleventy.config.js',
});

// Runs the incremental watch engine — per-change rebuilds, same as the CLI
await eleventy.watch();

You can also set incremental and ignoreInitial in the instance options or configuration so a custom build script behaves like --serve --incremental --ignore-initial without shelling out to the CLI. This is the hook to use when Eleventy is one stage of a larger Node pipeline — for example a script that starts a watcher, then triggers a downstream asset step only when Eleventy reports which outputs changed.

Pairing with expensive per-page work

Incremental builds compound with any work that scales per page. If you run @11ty/eleventy-img to generate responsive images inside a shortcode, a full build re-evaluates that shortcode for every page; an incremental rebuild only runs it for the page you edited. The image plugin keeps its own on-disk cache, so unchanged sources are skipped regardless, but skipping the template render itself is what turns a multi-second image-heavy save into a sub-second one. The same logic applies to syntax highlighting, remote-data fetches wrapped in the data cascade, and any custom transform — the fewer templates that re-render, the less of that per-page cost you pay on each keystroke.

Measured Impact

Benchmarked on a 500-page Eleventy 3.x site (Markdown posts, a shared Nunjucks layout, Node 22, Apple M2) using hyperfine for the full build and the dev-server console timings for incremental rebuilds:

OperationTimePages rebuilt
eleventy full build (cold)4.82 s500
Incremental rebuild — edit one post0.11 s1
Incremental rebuild — edit a shared partial0.34 s~40 (pages using it)
Incremental rebuild — edit base layout4.6 s500 (all)
--serve --ignore-initial startup0.2 s0 (no initial write)
Rebuild time by change type in Eleventy A horizontal bar chart of rebuild times on a 500-page site: a cold full build takes 4.82 seconds, editing one post rebuilds in 0.11 seconds (roughly 40 times faster and the headline win), editing a shared partial takes 0.34 seconds, and editing the base layout is a full 4.6-second rebuild. Bars share a common linear scale so the single-post bar reads as a thin sliver against the full-build bars. Rebuild time by what you changed Full build (cold) 500 pages 4.82 s Edit one post 1 page 0.11 s · ≈40× faster Edit shared partial ~40 pages 0.34 s Edit base layout 500 pages 4.6 s Shared linear scale · lower is faster · benchmarked on a 500-page Eleventy 3.x site (Node 22, M2)
The same rebuild on the same site, priced by what you touched. A leaf post edit is a sliver against the cold full build — about 40× faster — while editing the base layout is a genuine full rebuild, because every page depends on it.

The headline is the first incremental row: editing a single post went from a 4.82 s full build to 0.11 s, a roughly 40x improvement on the edit loop. The layout row is the reminder that incremental builds skip unaffected pages — when the file you change is a genuine dependency of everything, everything rebuilds, and that is correct behavior.

The full cold build time is the same number you would cache against in CI; for that side of the story see Caching node_modules in GitHub Actions for Faster SSG Builds and the broader GitHub Actions for Automated SSG Builds pipeline.

Pitfalls & Rollback

  • Expecting CI speedups. A clean CI checkout has no prior build state, so the first build is always full. Incremental builds help local editing, not cold runners — cache dependencies for CI instead.
  • Stale incremental state after a crash. If a watch session dies mid-build, on-disk state can desync and produce wrong output. Stop the server, delete the output directory, and run one clean eleventy build to reset.
  • Surprise at full rebuilds. Editing data files in the global _data directory or a base layout legitimately invalidates many pages. That is the dependency graph working, not incremental builds failing.
  • Stale listing pages. A collection reference is not a tracked dependency, so adding a post may leave your index, tag, or feed templates showing the old list. Edit the listing template or restart the server when you are working on one, and never trust an incremental run for the final production build.
  • Rollback: the flags are purely additive. Remove --incremental and --ignore-initial from your dev script and you are back to a plain full build on every change — no state to migrate.
What --incremental does and does not cover Two panels. Covered: the template you edited, templates that depend on it through layouts or includes, and passthrough files that changed. Not covered: listing pages that read collections, and anything in CI where there is no previous state. What --incremental does and does not cover Rebuilt correctly the template you saved anything including or extending it changed passthrough assets data files a template reads Can go stale index and tag pages using collections feeds and sitemaps anything computed across all pages every build in CI — no prior state Restart the dev server when working on a listing page, and never use --incremental for a production build.
The right-hand column is not a bug: a collection reference is computed at build time rather than declared, so the dependency graph cannot see it.

Conclusion

Incremental builds turn Eleventy's edit loop from "wait for the whole site" into "render the one page I touched." Add --incremental to your --serve script, layer in --ignore-initial on large sites for instant startup, and use the programmatic watch() if you embed Eleventy elsewhere. Just keep the model straight: it skips unaffected pages, so a leaf edit is near-instant while a shared-layout edit correctly rebuilds everything. For deploy-time speed, the lever is caching, not incremental flags — covered across Incremental Builds and Build Caching for SSGs.

FAQ

Does --incremental work without --watch or --serve?

It is designed to be paired with --watch or --serve, where Eleventy already knows which file changed. On a one-shot eleventy --incremental run with no prior cache there is nothing to diff against, so it behaves like a full build. The speedups come on the second and later rebuilds in a running watch session.

What does --ignore-initial actually do?

It tells Eleventy to skip writing files on the very first pass when it starts in watch or serve mode, then build only what changes afterward. Combined with --incremental it means startup is near-instant and you only pay for rendering the page you edit, which is ideal for a large site where you are touching one template.

Why did editing my layout rebuild every page?

Layouts and includes are dependencies of many pages, so Eleventy correctly rebuilds every page that uses them. Incremental builds skip unaffected pages, not pages that genuinely depend on the file you changed. Editing a single leaf Markdown file rebuilds one page; editing a base layout rebuilds everything that inherits it.

Can I use incremental builds in CI?

Rarely usefully. A CI runner starts from a clean checkout with no previous build state, so the first build is always full. Incremental builds help local editing and long-lived dev servers. For CI speed, cache node_modules and any generated caches between runs instead.

Does the programmatic API support incremental builds?

Yes. Construct an Eleventy instance and call eleventy.watch(), which runs the same incremental engine as the CLI watch mode. Passing incremental through the configuration or instance options lets you embed Eleventy in a larger Node process and still get per-change rebuilds.