This commit is contained in:
Raven Scott
2026-07-11 20:01:07 -04:00
parent 77991dd871
commit 5274e0e2fc
+24 -25
View File
@@ -256,7 +256,9 @@ function sitemapChangefreq(urlPath) {
/**
* Build a Google Searchcompatible sitemap 0.9 document from dist HTML pages.
* Strict sitemaps.org protocol only (loc, lastmod, changefreq, priority).
* @see https://www.sitemaps.org/protocol.html
* @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap
*/
function buildSitemapXml() {
const pages = walkHtmlFiles(DIST)
@@ -268,64 +270,61 @@ function buildSitemapXml() {
const excludeKey = urlPath.replace(/\/+$/, '') || '/'
if (SITEMAP_EXCLUDE.has(excludeKey) || SITEMAP_EXCLUDE.has(urlPath)) continue
const lastmod = page.mtime.toISOString().slice(0, 10) // W3C date (YYYY-MM-DD)
// W3C Datetime (date only is accepted by Google)
const lastmod = page.mtime.toISOString().slice(0, 10)
const existing = byPath.get(urlPath)
if (existing && existing.lastmod >= lastmod) continue
// Absolute HTTPS loc only — required by the protocol
const loc = SITE_ORIGIN + (urlPath === '/' ? '/' : urlPath)
byPath.set(urlPath, {
loc: SITE_ORIGIN + (urlPath === '/' ? '/' : urlPath),
loc,
lastmod,
changefreq: sitemapChangefreq(urlPath),
priority: sitemapPriority(urlPath),
})
}
// Stable sort: home first, then path
const entries = [...byPath.values()].sort((a, b) => {
if (a.loc === `${SITE_ORIGIN}/`) return -1
if (b.loc === `${SITE_ORIGIN}/`) return 1
return a.loc.localeCompare(b.loc)
})
const lines = [
// One declaration + default namespace only (no xhtml extras — single-language site)
const parts = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"',
' xmlns:xhtml="http://www.w3.org/1999/xhtml">',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
]
for (const e of entries) {
lines.push(' <url>')
lines.push(` <loc>${escapeXml(e.loc)}</loc>`)
lines.push(` <lastmod>${escapeXml(e.lastmod)}</lastmod>`)
lines.push(` <changefreq>${escapeXml(e.changefreq)}</changefreq>`)
lines.push(` <priority>${escapeXml(e.priority)}</priority>`)
// Self-referencing hreflang (single-language site)
lines.push(
` <xhtml:link rel="alternate" hreflang="en" href="${escapeXml(e.loc)}" />`
parts.push(
' <url>',
` <loc>${escapeXml(e.loc)}</loc>`,
` <lastmod>${escapeXml(e.lastmod)}</lastmod>`,
` <changefreq>${escapeXml(e.changefreq)}</changefreq>`,
` <priority>${escapeXml(e.priority)}</priority>`,
' </url>'
)
lines.push(
` <xhtml:link rel="alternate" hreflang="x-default" href="${escapeXml(e.loc)}" />`
)
lines.push(' </url>')
}
lines.push('</urlset>')
lines.push('')
return lines.join('\n')
parts.push('</urlset>')
// Trailing newline for POSIX-friendly files
return parts.join('\n') + '\n'
}
function serveSitemap(req, res) {
try {
const xml = buildSitemapXml()
// text/xml is what Google documents for sitemaps; charset for clarity
const headers = {
'Content-Type': 'application/xml; charset=utf-8',
// Short cache so new pages appear soon after deploys
'Content-Type': 'text/xml; charset=utf-8',
'Cache-Control': 'public, max-age=300',
'X-Robots-Tag': 'noindex', // the sitemap document itself need not be indexed as a page
}
if (req.method === 'HEAD') {
res.writeHead(200, {
...headers,
'Content-Length': Buffer.byteLength(xml),
'Content-Length': Buffer.byteLength(xml, 'utf8'),
})
res.end()
return