Table of Contents

v0.1.0-next.48

The big one is dates. Baseline no longer uses Eleventy's page.date, which on CI is the moment your repository was cloned rather than anything to do with your content. It resolves a publish and a modified date from an ordered list of sources you can change, keeps a calendar date as a calendar date, and emits nothing at all when it has nothing to go on.

That last part is the change most likely to show up in your output. A page with no date now carries no date. Read the first breaking note before you upgrade, because the fix is one line of config and the symptom is quiet.

Second, built asset filenames carry a content hash, so you can finally serve them immutable for a year. Anything declared in settings.head is resolved for you, so most sites need no edit; a layout naming a file directly gets the new asset filter. Entry points are compiled once at the start of the build and recorded with their hash, size and SRI value, readable from a template.

And three smaller ones: a sitemap emits the changefreq and priority you wrote in front matter, which it has never done; a page-break marker can name the first part of a split page; and a declared asset stops being reported missing when fingerprinting renamed it.


Breaking

  • A page with no resolvable date now has no date.

    datePublished used to fall back to Eleventy's page.date, which Eleventy fills in from the file's creation time when your front matter says nothing. On a CI build that is the clone time, so every page in a build shared one publish date, to the millisecond, and it moved forward on every deploy. It usually landed after the modified date.

    Baseline now tries, in order: your datePublished key, your date key, then a YYYY-MM-DD in the file name. If none of those answer, the field is absent. The JSON-LD omits the property, and the sitemap entry has no <lastmod> element.

    That is deliberate. datePublished is optional on a WebPage, and an absent date is honest where a wrong one is not.

    • Symptom: datePublished disappears from the structured data on pages that never had a real date, and some <lastmod> elements leave your sitemap. If you want them back, put the date in front matter, or add ':git-created' to the source list and accept that it dates a page to its first commit.
  • A calendar date stays a calendar date.

    datePublished: 2026-04-28 in your front matter now emits 2026-04-28, where it used to emit 2026-04-28T00:00:00.000Z. Both the sitemap's <lastmod> and schema.org's date properties accept a day on its own, so nothing is lost, and the day no longer shifts for a reader in another time zone.

    Add a time if you want an instant. A time with no offset is read in settings.timeZone, which is new and defaults to UTC.

    • Symptom: shorter date strings in your JSON-LD and your sitemap. Validators accept both forms.
  • The date template global is gone.

    It had one method, date.toUTCISO(value). Every date Baseline emits now arrives at the template as a finished string, so there was nothing left for it to convert, and it was the last place a calendar date could be widened back into a midnight timestamp.

    • Symptom: a template calling date.toUTCISO(...) throws. Print the value directly instead. For a human-readable date, reach for Intl.DateTimeFormat with the locale and zone Baseline resolved for the page.
  • page.date is no longer on the page context, and date is no longer on a content graph node.

    Both carried Eleventy's raw date object. They now carry datePublished and dateModified as resolved strings, in both places: _pageContext.page and every entry in _navigator.nodes.

    • Symptom: _navigator.nodes[url].date and _pageContext.page.date are undefined. Eleventy's own page.date is untouched and still there if you want it.
  • Built asset filenames now carry a content hash.

    assets/css/index.css is written as assets/css/index.342303cd.css, and the same for JS entry points. Only in a build; --serve keeps the plain names so the dev server stays predictable.

    The point is caching. A stable filename makes a long Cache-Control header unsafe, which is why every project ends up hand-rolling a ?v= query string. With the hash in the name you can serve assets immutable for a year.

    Anything you declared in settings.head is resolved for you, so a site that routes its stylesheets and scripts through settings changes nothing and needs no edit. The new asset filter covers a layout that names a file directly. Set assets: { fingerprint: false } to turn it off.

    • Symptom: your dist/assets/ filenames change, and a hardcoded /assets/css/index.css in a template that goes through neither settings.head nor the asset filter now 404s. Grep your layouts for literal asset paths.
  • The sitemap's computed entry moved to _sitemap.

    It was written to eleventyComputed.page.sitemap and read from item.data.sitemap, so the two never met. Three of the four values it carries were unreachable as a result.

    Your sitemap front-matter key is unchanged and is still where you author ignore, lastmod, changefreq and priority. Only the resolved object moved.

    • Symptom: none, unless you read page.sitemap in a template of your own, which would have been reading the dead defaults layer.

Added

  • settings.timeZone. Optional, defaults to UTC. An IANA name such as 'Europe/Amsterdam'. It anchors a date-time written without an offset; a value carrying its own offset is converted from that, and a calendar date is not a moment, so neither is affected.

    Validated by handing it to Intl, so any name, alias or casing your platform accepts works. A bare offset like +02:00 is refused, because it has no daylight saving and would be wrong for half the year.

  • options.dates. Replace the source list for either field. :default expands to the built-in list in place, so you can prepend a key of your own without restating the rest:

    eleventyConfig.addPlugin(baseline(settings, {
        dates: {
            datePublished: ['pubDate', ':default']
        }
    }));

    A plain name is a front-matter key. The derived sources are :filename, :git-created, :git-modified and :datePublished. Filesystem modification time is not a source and Baseline refuses it by name, for the reason the first breaking note gives.

    The git sources read one git log walk, cached once per build, and take the author date rather than the committer date, so rebasing does not restamp a page. They are skipped entirely outside a repository and on a shallow clone, where every file reports the same commit, and Baseline says one line about it rather than silently resolving nothing. GitHub Actions clones at depth 1 by default: set fetch-depth: 0 if you want them. Neither line appears unless your source lists name a git token.

  • An asset index, exposed as _assets. Each CSS and JS entry point is compiled once at the start of the build and recorded under the path you would write for it. A template can read the url, hash, integrity value and size:

    <script
        src="{{ _assets['/assets/js/index.js'].url }}"
        integrity="{{ _assets['/assets/js/index.js'].integrity }}"
        crossorigin="anonymous"
        defer
    ></script>

    url is the fingerprinted path, which is what you want in a tag. hash is the content hash on its own, size is the byte count, and integrity is ready to drop into an SRI attribute.

  • Named entry points, with postcss.entryPoints and esbuild.entryPoints. Each processor's own options bag, rather than a new key beside them.

    assets: {
        postcss: { entryPoints: ['main.css'] },
        esbuild: { entryPoints: ['index.js', 'vendor/widget.js'] }
    }

    Paths are relative to that source's own directory, so 'vendor/widget.js' rather than 'assets/js/vendor/widget.js'. A declared list replaces the index.css and index.js convention for that processor instead of adding to it, so what you write is exactly what runs.

  • An asset filter, for a template that names a file directly rather than through settings.head:

    <link rel="stylesheet" href="{{ '/assets/css/index.css' | asset }}">

    It takes the path you wrote and returns the one the build produced. Most pages do not need it, because the head module already resolves what settings declare, and keeping asset paths in one place is the better habit.

  • A page-break marker above all of your content names the first part. A marker names the part that follows it, and nothing follows nothing, so part one could only ever report its number. Write a marker at the top and it names part one without cutting anything, because the piece it would cut is empty.

    No part is added and no URL moves. Two sites had already worked around this independently, one hardcoding a label in its layout and one inventing a front-matter key.

  • dir.cache moves Baseline's cache. Both the content graph cache and the media cache are anchored beside your input directory rather than resolved against the working directory, and dir.cache in your returned config moves them together. The default is unchanged at .cache.


Fixed

  • A sitemap emits the changefreq and priority you authored. It never has. The module wrote its computed entry to one path and its template read another, so unless you authored lastmod in front matter, three of the four values were dropped on the floor. lastmod could not be overridden either.

    • Symptom: <changefreq> and <priority> elements appear for pages whose front matter declares them. If you have been setting them and seeing nothing, that is why.
  • <lastmod> comes from the resolved modified date. Which is the last commit to touch the file unless your front matter says otherwise, rather than a file timestamp that moves on every deploy. A page with no resolvable date gets no <lastmod> element rather than a wrong one.

  • A language's own lastmod is emitted as written. If you set lastmod on an entry in settings.languages, the sitemap index formatted it into a full timestamp. It is resolved like any other date now, so a calendar date stays the day.

    • Symptom: a shorter <lastmod> in /sitemap.xml on a multilingual site that sets this. Almost nobody does.
  • A computed schema.pieces reaches the graph. The documented extension seam did nothing on any page without pagination, which is most pages of most sites.

    Eleventy orders computed keys by what each function is seen to read. Baseline's returned early before reading anything, so it was scheduled ahead of the values it consumes. It now declares them. The same applies to a computed ogImage and seo.ogImage.

    • Symptom: schema nodes you wrote in a computed function start appearing in your JSON-LD. If you moved them to static data to work around this, you can move them back.
  • A declared asset is checked against the file the build wrote. settings.head entries naming a stylesheet or script were tested against the name you declared, while the build writes the fingerprinted one, so every entry point was reported as a missing file.

    • Symptom: one spurious declares ... which the build did not write warning per declared entry point, gone. Three of them on this site.
  • A date Baseline cannot read is one warning, not six. Three separate resolvers read the same page's dates, and Eleventy runs a computed function twice, once to work out what it reads and once for real, so a single bad value produced six identical lines.

    One of them owns the message now, and repeats are dropped. The message itself is unchanged: it names the page, the key and the value it could not read.

    • Symptom: a quieter build if you have an unreadable date. Nothing to do.
  • The head module's count is the number of pages it wrote. It counted input files, so a paginated template or a split page counted once however many pages it produced. On this documentation site the line read 85 against 103 files on disk, and had been wrong by eighteen for about two months.

    • Symptom: a bigger number in Head injected into N pages. Nothing about your output changes.

Internal

  • The pre-pass re-entry guard asks the Eleventy config object whether it is the nested build, through a registry scope, rather than reading a process environment variable. Two builds in one process could read each other's, and an eleventy.before handler that checked the variable raced its own siblings, which left the asset index unbuilt in both passes on an otherwise green build.

    BASELINE_PREPASS_ACTIVE is still published and still set, because documented recipes read it. Nothing inside Baseline reads it any more.

  • The navigator's debug page no longer renders during the content-graph pre-pass, which saves 161ms of a 2.4s pre-pass here. It is a debug surface with nothing the graph extracts.

  • The collection-built translation map moved out of a closure into its own module, which is what made it testable. The two producers of a page's translation siblings key on different things, and the extraction is a guard against that mattering later rather than a fix for anything live.

  • The dates substrate is new at core/dates/, with the page.date fallback chain deleted. One resolver answers for the JSON-LD graph, the Open Graph tags, the sitemap, the schemamap, the page context and the content graph, so no two surfaces can disagree about when a page was published.

  • The registerGlobals aggregator is gone from the surface barrel, the date global having been its only member.


Docs

  • Dates is a new reference page: why the substrate exists, the date-versus-instant model, the source lists, the time zone, git as a source, and where the resolved dates surface.
  • The core reference chapter gained rows for settings.timeZone, options.dates, and the asset index globals.