Eleventy SEO

Eleventy (11ty) ships zero client-side JavaScript by default, so your content is in the raw HTML on the first fetch. The catch — it generates no title tags, meta descriptions, canonicals, or sitemaps. Here's how to configure SEO on an Eleventy site, built around the data cascade.

First published: Jun 26, 2026 · Last updated: Jul 17, 2026 · Advanced
demand #4 in Static Site Generators#47 in Platform SEO#267 in Technical SEO#360 on the site
1 evidence signal on this page

Eleventy outputs plain HTML with zero client-side JavaScript by default, which is the safest possible starting point for indexability — content is in the first byte, nothing to render. The trade-off: Eleventy generates no SEO markup at all. Every title, meta description, canonical, sitemap, and bit of structured data is something you implement yourself. The data cascade (_data/site.js → directory data → front matter → eleventyComputed) is the architecture that makes that scalable: declare defaults once, derive canonicals automatically. Plugins like eleventy-plugin-seo and @11ty/eleventy-img add convenience, not capability. Pick any template language — it doesn't affect SEO.

TL;DR — Eleventy emits plain HTML at build time with zero client-side JS by default, so content is in the response before the first crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. request — the lowest-risk indexability setup there is. But Eleventy ships no SEO markup: title, description, canonical, robots, OG, sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing., and JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. are all yours to build. The data cascade (_data/site.js → directory data files → front matter → eleventyComputed) is the architecture that makes this scale — declare defaults once, derive canonicals in eleventyComputed. Plugins (eleventy-plugin-seo, @11ty/eleventy-img, @quasibit/eleventy-plugin-sitemap) add convenience, not capability. robots.txt is a passthrough file. Template language is an SEO non-issue — pick by team familiarity. v3 is ESM-first and makes the Image Transform plugin the default path to CWVGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data.-clean images. Watch three edge cases: eleventyComputed dependency ordering can fail silently, collection membership doesn’t guarantee a URL was emitted, and a local build is not proof of what production actually serves — verify the deployed artifact directly.

Why Eleventy is a strong SEO baseline

Eleventy builds every route to static HTML at build time and, unlike Next.js, Nuxt, Gatsby, or Astro, ships no JavaScript to the browser unless you add it. Evidence for this claim Eleventy compiles templates to static output and adds no client-side framework runtime by default. Scope: Eleventy core behavior. Confidence: high · Verified: Eleventy documentation There’s no client-side renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. for a crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. to wait on, no renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. budget impact, and no hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. bundle taxing Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data.. Real production Eleventy sites routinely report Lighthouse scoresLighthouse is Google's free, open-source tool that audits a page under simulated lab conditions and scores it 0–100 across Performance, Accessibility, Best Practices, and SEO. It's lab data — useful for debugging, not a ranking signal. of 99–100 across the board and homepages that ship 0 KB of JavaScript.

It’s also fast to build, which matters more for SEO than it looks: faster builds mean tighter deploy cycles, so content fixes reach search engines sooner. On a 4,000-Markdown-file benchmark Eleventy builds in ~1.93s versus ~22.9s for Astro, ~29s for Gatsby, and ~70.6s for Next.js, with a fraction of the node_modules footprint (34 MB vs Gatsby’s 583 MB).

But — and this is the whole point of the page — none of that buys you SEO markup. Eleventy generates clean HTML and stops there. Every <title>, meta description, canonical, sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. entry, and JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. block is something you implement. The good news is that Eleventy’s data model makes implementing them once-and-everywhere genuinely pleasant.

The data cascade: your SEO architecture foundation

The single most important concept for scalable Eleventy SEOEleventy (11ty) is a JavaScript-based static site generator that outputs plain HTML with zero client-side JavaScript by default. Eleventy SEO is the practice of explicitly configuring that output — meta tags, canonicals, sitemaps, images, and structured data — because Eleventy generates none of it for you. is the data cascade — the order in which Eleventy resolves a value, lowest priority to highest: Evidence for this claim Eleventy's data cascade resolves data from multiple sources according to a documented priority order. Scope: Eleventy data cascade. Confidence: high · Verified: Eleventy: Data cascade

  1. Eleventy-supplied global data
  2. Global data files in _data/ (e.g. _data/site.js with title, description, url, author)
  3. Directory data files (e.g. blog/blog.11tydata.js — give every blog post og:type: article)
  4. Template front matter (per-page overrides)
  5. eleventyComputed (highest — derive values from other data)

In practice:

  • Declare your SEO defaults once in _data/site.js.
  • Override at the directory level for a content type (all posts get the article OG type, a noindex section gets noindex, etc.).
  • Override per page in front matter where a page is special.
  • Use eleventyComputed for derived values — the canonical being the canonical example:
// _data/eleventyComputed.js  (or eleventyComputed in a layout data file)
module.exports = {
  canonical: (data) => `${data.site.url}${data.page.url}`,
};

Now every page has a correct self-referencing canonical with zero per-page work. That’s the leverage the cascade gives you.

One edge case worth testing rather than assuming: eleventyComputed values can depend on other computed values (a title that feeds into a computed description, say), and Eleventy has to resolve that dependency graph before it can render anything. Straightforward chains work fine, but a computed value that accidentally depends on itself — directly or through another computed field — is a circular reference, and ordering issues here fail silently as a missing or wrong value rather than a loud build error. If you lean on multiple layered eleventyComputed fields, check the rendered output for the exact values you expect, not just that the build succeeded. Evidence for this claim Computed Data values can derive from other computed values, so dependency ordering and circular references need testing against the rendered output. Scope: Eleventy eleventyComputed resolution. Confidence: high · Verified: Eleventy: Computed Data

Meta tags in the base layout

Standard architecture is a base layout that owns the <head>, with content layouts extending it:

_includes/
  layouts/
    base.njk      ← <head> with all SEO meta tags
    post.njk      ← extends base, adds BlogPosting schema
_data/
  site.js         ← sitewide SEO defaults

The meta tagsMeta tags are HTML elements in a page's head that pass metadata about the page to search engines and browsers. For SEO only a few matter — the title element, the meta description, and the robots meta tag — while meta keywords and most others are ignored. you need to implement yourself, at minimum:

  • <title> — unique per page
  • <meta name="description"> — unique per page
  • <link rel="canonical"> — Eleventy has no built-in canonical; add it explicitly
  • <meta name="robots">noindex for draft/preview pages (and thin paginated pages, if any)
  • Open GraphOpen Graph (OG) tags are `<meta>` elements in a page's head, defined by the Open Graph protocol (ogp.me, created by Facebook), that describe a page as a shareable object — its title, description, image, URL, and type. They control how a link preview card looks when the page is shared on Facebook, LinkedIn, Slack, Discord, WhatsApp, and iMessage. They are not a direct Google ranking factor, though Google reads og:title, og:image, and og:site_name as inputs to how a result appears.og:title, og:description, og:image, og:url, og:type
  • Twitter/X Cards — twitter:card, twitter:title, twitter:description, twitter:image

A minimal Nunjucks <head> pulling from the cascade:

<title>{{ title }} | {{ site.title }}</title>
<meta name="description" content="{{ description or site.description }}">
<link rel="canonical" href="{{ canonical }}">
<meta property="og:title" content="{{ title }}">
<meta property="og:image" content="{{ ogImage or site.defaultImage }}">
<meta name="twitter:card" content="summary_large_image">

SEO plugins, compared

You don’t need a plugin — a manual base layout plus the data cascade gives you full control with no dependencies, and many practitioners prefer exactly that. But two plugins are worth knowing:

  • eleventy-plugin-seo (artstorm) — the most mature and widely used. A single {% seo %} shortcode (Liquid) / {% seo "" %} (Nunjucks) emits title, description, canonical, robots, Open GraphOpen Graph (OG) tags are `<meta>` elements in a page's head, defined by the Open Graph protocol (ogp.me, created by Facebook), that describe a page as a shareable object — its title, description, image, URL, and type. They control how a link preview card looks when the page is shared on Facebook, LinkedIn, Slack, Discord, WhatsApp, and iMessage. They are not a direct Google ranking factor, though Google reads og:title, og:image, and og:site_name as inputs to how a result appears., Twitter Card, and author from a config block (site title, description, URL, author, Twitter handle, default image). It also handles robot directives on paginated pages, a title divider, and a minimalistic mode. Install: npm install eleventy-plugin-seo.
  • eleventy-plugin-metagen (tannerdolby) — more granular, with named shortcode parameters; generates charset, viewport, title, author, description, generator, OG, Twitter Card, canonical, and CSS/JS tags.

The rule of thumb: plugins add convenience, not capability. Anything they do, you can do in the base layout. Reach for one if you’d rather not maintain the boilerplate.

robots.txt

Eleventy does not auto-generate robots.txt. Two approaches:

1 — Static passthrough (simplest). Drop a robots.txt in your source and copy it verbatim:

eleventyConfig.addPassthroughCopy("src/robots.txt");

2 — Template-generated (for environment-specific output — block everything on staging, allow on production):

---
permalink: /robots.txt
eleventyExcludeFromCollections: true
---
User-agent: *
{% if environment == "production" %}
Allow: /
Sitemap: {{ site.url }}/sitemap.xml
{% else %}
Disallow: /
{% endif %}

Remember the universal rule: Disallow blocks crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor., not indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. — and a noindex tag on a crawl-blocked URL is never seen.

Sitemap generation

Also not built in. The most-control option is a manual template — create sitemap.xml.njk with permalink: /sitemap.xml and eleventyExcludeFromCollections: true, loop over collections.all, skip excluded pages, and emit <loc> / <lastmod> entries:

---
permalink: /sitemap.xml
eleventyExcludeFromCollections: true
---
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for page in collections.all %}
  {% if not page.data.excludeFromSitemap %}
  <url>
    <loc>{{ site.url }}{{ page.url }}</loc>
    <lastmod>{{ page.date | dateToISO }}</lastmod>
  </url>
  {% endif %}
{% endfor %}
</urlset>

Or use @quasibit/eleventy-plugin-sitemap (configure with hostname; it also supports multilingual alternate links). Either way, mark utility pages with eleventyExcludeFromCollections: true so they don’t leak in.

One thing worth being precise about: looping over collections.all walks collection membership, not guaranteed output. A template can belong to a collection and still not emit a URL — most commonly because its permalink is set to false, or because a data-only file was never meant to render a page in the first place. Membership in collections.all is not proof an item belongs in the sitemap; check the rendered <loc> entries against the pages you actually expect to be crawlable. Evidence for this claim Collection membership does not by itself guarantee that an item emits a URL or belongs in the sitemap. Scope: Eleventy collections and permalink interaction. Confidence: high · Verified: Eleventy: Collections

TIP Validate the generated sitemap artifact before deployment

A correct Nunjucks loop can still inherit a bad hostname or emit a relative location. Validate the built XML itself—the file crawlers receive—not only the Eleventy configuration.

Check the output with my free XML Sitemap Validator Free

  1. Run the production build and validate its actual sitemap URL or XML.
  2. Fix malformed XML, relative locations, duplicate entries, and invalid lastmod values at the data or template source.
  3. Rebuild and confirm excluded utility and noindex pages remain absent.
A missing or incorrect site hostname can quietly turn otherwise valid Eleventy paths into unusable sitemap entries.

The validator reports that the loc element is not an absolute URL and says sitemap URLs must be fully qualified rather than relative paths.

Image optimization for Core Web Vitals

A static site’s HTML is fast, but unoptimized images will still tank LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. and CLSCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good.. The fix is @11ty/eleventy-img, and in v3 the recommended path is the Image Transform plugin, which post-processes every <img> tag in your built HTML automatically — no per-image shortcode needed:

const { eleventyImageTransformPlugin } = require("@11ty/eleventy-img");

eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
  formats: ["avif", "webp", "jpeg"],
  defaultAttributes: {
    loading: "lazy",
    decoding: "async",
  },
});

What that buys you for SEO:

  • Auto-stamped width and height → prevents layout shift (CLS)
  • srcset with multiple widths → responsive images
  • <picture> with AVIF + WebP + JPEG fallback
  • No upscaling

For above-the-fold hero images, override the lazy default so they load eagerly:

<img src="hero.jpg" loading="eager" fetchpriority="high" eleventy:widths="800,1200">

Eleventy’s default is clean URLs with a trailing slashA trailing slash is the forward slash (/) at the end of a URL — example.com/page/ versus example.com/page. Except at the bare root domain, the two versions are different URLs to search engines, so you pick one format and enforce it.: about.njk_site/about/index.html/about/. Override in front matter:

permalink: "/blog/{{ title | slugify }}/"

Useful flags: permalink: false processes a template for collections but skips writing it to disk (handy for data-only templates), and dynamicPermalink: false treats the permalink string literally. The one SEO thing to stay disciplined about: trailing-slash consistency — mixing /page and /page/ is a duplicate-content own-goal.

Worth stating plainly because it trips people up: permalink: false suppresses output entirely — the source file compiles and can still feed collections and data, but no HTML page is written and no URL exists to crawl. A source file sitting in your content directory is not proof that a corresponding page shipped; if a page is missing from search, check the built _site output for the file before assuming it’s an indexing problem rather than a permalink one. Evidence for this claim A permalink value of false suppresses page output entirely, so a source file's existence is not proof of a crawlable page. Scope: Eleventy permalink output control. Confidence: high · Verified: Eleventy: Permalinks

Pagination SEO

Eleventy’s paginationPagination splits a large set of content — product listings, blog archives, search results — across multiple sequentially numbered URLs. For SEO, each paginated page should be crawlable, indexable, and self-canonical; Google no longer uses rel=prev/next, but Bing still does. splits a collection across output files. The first page is /blog/, then /blog/1/, /blog/2/, and so on (customizable via permalink). Each page in the set needs a unique URL and pagination.alias to expose per-page data. The SEO decisions:

  • Canonicals. Two schools: point every page at page 1 (consolidates signals but buries legitimate deep-page content), or self-referencing canonicals where each page’s canonical is its own URL — Google’s preferred approach in current documentation. I default to self-referencing unless the deeper pages truly have nothing worth indexing.
  • rel="prev" / rel="next". Google confirmed back in 2019 it no longer uses these as ranking signals. Some still add them to help crawlers understand series structure; if you do, wire them from pagination.href.previous / pagination.href.next.
  • noindex on thin pages. If later pages have insufficient content, add noindex (via the noindex field if you’re using eleventy-plugin-seo, or manually in the head).

Structured data / JSON-LD

Manual is the most flexible: define a schema object in eleventyComputed or a directory data file and serialize it in the layout. The cleanest types for a content site are Organization / WebSite sitewide in the base layout, BlogPosting / Article per post, BreadcrumbList if you have breadcrumbsBreadcrumbs are a secondary navigation trail (Home > Category > Page) that shows where a page sits in a site's hierarchy. They create internal links that pass PageRank, and when marked up with BreadcrumbList structured data they can drive the path Google shows in desktop search results., and FAQPage on FAQ sections. @quasibit/eleventy-plugin-schema offers a shortcode for BlogPosting and WebPage if you’d rather not hand-roll it (it expects ISO 8601 dates, so register a date filter).

Eleventy v2 vs v3 — what changed for SEO

v3 (current stable) is ESM-first: a config file that uses require() now needs the .cjs extension, or you set "type": "module" in package.json and use import. CommonJS is still fully supported — just be deliberate about it. The two SEO-relevant wins:

  • ~38% faster cold builds on sites over 500 pages → faster deploys, fresher index.
  • The Image Transform plugin is the default path — declarative post-processing of every <img>, which makes shipping responsive, CLS-safe images the path of least resistance instead of a per-image chore.

Verify the production artifact, not just the local build

Everything above is about getting the source right — the data cascade, the layout, the plugins. None of it proves what actually ships. A local _site build (or eleventy --serve) doesn’t tell you what production serves: the host and path your deploy platform actually uses, redirectsA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't. it injects, headers it sets, or whether a CDN/proxy rewrites anything on the way out. Confirm the installed Eleventy version matches what you’re testing against, then check the deployed site directly, not just the build output:

  • Raw HTML — view-source the live URL, not just _site/index.html; confirm the title, meta descriptionThe meta description is an HTML head tag — `<meta name=\"description\" content=\"…\">` — that suggests a short summary of the page for the search snippet. It's not a Google ranking factor, and Google rewrites it the majority of the time, but a good one can still lift click-through., and canonical are there and correct.
  • Absolute canonical host — a canonical built from a staging or localhost site.url will silently ship wrong in production if that value wasn’t overridden per environment.
  • Sitemap XML — fetch the live /sitemap.xml and confirm it resolves, uses the production host, and lists the URLs you expect (see the sitemap section above on collection membership not guaranteeing an entry).
  • Suppressed/utility pages — spot-check that permalink: false and eleventyExcludeFromCollections: true pages are actually absent from the live site, not just from your local build.
  • Assets — CSS, JS, and images referenced in the built HTML resolve on the production host (a relative-vs-absolute path mismatch is easy to miss locally).
  • Status codes and redirects — request the live URLs directly and confirm 200s where you expect 200s; a deploy platform’s redirect rules can silently turn a canonical page into a 301 chain that never shows up in a local build.

Third-party plugin installation is not proof of correct output either — it changes what gets generated, not what a specific deploy environment does with it. Treat this as a release-checklist item, not a one-time setup. Evidence for this claim Local build output does not by itself prove production hostnames, redirects, headers, or canonical behavior; the deployed artifact needs its own verification pass. Scope: Eleventy production deployment verification. Confidence: medium · Verified: Patrick Stox: Eleventy SEO

Common Eleventy SEO mistakes

  • Assuming Eleventy “handles SEO” — it handles HTML; the SEO markup is all yours.
  • Forgetting the canonical entirely (there’s no built-in one).
  • Shipping no sitemap because you assumed it was automatic.
  • Letting unoptimized images undo the speed advantage.
  • Inconsistent trailing slashes creating duplicate URLs.
  • Using Disallow in robots.txt to try to deindex a page (use noindex instead).
  • Leaving draft/preview pages indexable because no noindex was set on the section.

For where Eleventy sits among the other generators, see the Static Site GeneratorsA build tool that pre-renders every page into static HTML files at deploy time, so the server delivers complete HTML without executing code per request — eliminating JavaScript rendering delays for search engine crawlers. hub; for the rendering theory behind why static output is low-risk, the parent JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. hub.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.