Skip to content

Commit 1262e49

Browse files
committed
seo: give each sitemap-index child a lastmod
The index listed three children with no dates, so the only safe read was "re-fetch all three". Each child now carries the newest lastmod among its own URLs. Derived by rendering the child and scraping its <lastmod> values back out, rather than recomputing them. The resolution order (apiReleased -> dateModified -> pageDates -> item.date) plus the isPage/noindex/`sitemap: false` filtering is intricate, and sitemap-urls.html is deliberately its only home; a second copy here would be free to drift, and a drifted lastmod is worse than none — Google discounts lastmod site-wide once it catches the file lying, and that discount would land on the blog entries where the dates are real. Rendering twice is free in a static build. Dates are YYYY-MM-DD, so `sort | last` is a correct maximum. Verified the output against an independently computed max per child, and confirmed the dates are real rather than build timestamps: sitemap-api.xml is 106 URLs at 2026-07-28 (the npm release date via apiReleased) plus exactly one at today, /api/, which is hand-authored and dated from git history. Same shape in the blog bucket. .com is untouched: sitemapBuckets is empty there, so it stays a flat 4-URL urlset and never enters this branch.
1 parent 18cf278 commit 1262e49

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

src/sitemap.liquid

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,39 @@ need an index, and empty children are a GSC error.
1414
<?xml version="1.0" encoding="UTF-8"?>
1515
{%- if sitemapBuckets.size > 0 -%}
1616
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
17+
{%- comment -%}
18+
Each child carries a <lastmod> = the newest lastmod among its own URLs, so a
19+
crawler can skip a bucket that has not changed. Without it the index says only
20+
"here are three files", and re-fetching all three is the only safe move.
21+
22+
Why it is derived by rendering the child rather than recomputed: the lastmod rules
23+
are genuinely intricate (apiReleased -> dateModified -> pageDates -> item.date, plus
24+
the isPage/noindex/`sitemap: false` filtering) and sitemap-urls.html is deliberately
25+
their only home. Reimplementing them here — or in a JS filter — would create a second
26+
copy free to drift, and a drifted lastmod is worse than none: Google discounts
27+
lastmod site-wide once it catches the file lying, and that discount would land on the
28+
blog entries where the dates are real.
29+
30+
So we render the bucket, scrape the <lastmod> values back out, and take the max.
31+
Dates are YYYY-MM-DD, so `sort | last` is a correct maximum (lexicographic ordering
32+
matches chronological for ISO-8601). Rendering each child twice is free in a static
33+
build. Buckets whose URLs carry no lastmod at all (author/topic pages emit none)
34+
simply get no <lastmod>, which is valid.
35+
{%- endcomment -%}
1736
{%- for b in sitemapBuckets %}
37+
{%- capture bucketXml -%}{%- include "sitemap-urls.html", bucket: b.name -%}{%- endcapture -%}
38+
{%- assign found = "" -%}
39+
{%- assign chunks = bucketXml | split: "<lastmod>" -%}
40+
{%- for c in chunks offset: 1 -%}
41+
{%- assign d = c | split: "</lastmod>" | first | strip -%}
42+
{%- assign found = found | append: d | append: "," -%}
43+
{%- endfor -%}
44+
{%- assign dateList = found | split: "," | sort -%}
1845
<sitemap>
1946
<loc>{{ siteUrl }}/sitemap-{{ b.name }}.xml</loc>
47+
{%- if dateList.size > 0 %}
48+
<lastmod>{{ dateList | last }}</lastmod>
49+
{%- endif %}
2050
</sitemap>
2151
{%- endfor %}
2252
</sitemapindex>

0 commit comments

Comments
 (0)