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. * 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://www.sitemaps.org/protocol.html
* @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap
*/ */
function buildSitemapXml() { function buildSitemapXml() {
const pages = walkHtmlFiles(DIST) const pages = walkHtmlFiles(DIST)
@@ -268,64 +270,61 @@ function buildSitemapXml() {
const excludeKey = urlPath.replace(/\/+$/, '') || '/' const excludeKey = urlPath.replace(/\/+$/, '') || '/'
if (SITEMAP_EXCLUDE.has(excludeKey) || SITEMAP_EXCLUDE.has(urlPath)) continue 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) const existing = byPath.get(urlPath)
if (existing && existing.lastmod >= lastmod) continue if (existing && existing.lastmod >= lastmod) continue
// Absolute HTTPS loc only — required by the protocol
const loc = SITE_ORIGIN + (urlPath === '/' ? '/' : urlPath)
byPath.set(urlPath, { byPath.set(urlPath, {
loc: SITE_ORIGIN + (urlPath === '/' ? '/' : urlPath), loc,
lastmod, lastmod,
changefreq: sitemapChangefreq(urlPath), changefreq: sitemapChangefreq(urlPath),
priority: sitemapPriority(urlPath), priority: sitemapPriority(urlPath),
}) })
} }
// Stable sort: home first, then path
const entries = [...byPath.values()].sort((a, b) => { const entries = [...byPath.values()].sort((a, b) => {
if (a.loc === `${SITE_ORIGIN}/`) return -1 if (a.loc === `${SITE_ORIGIN}/`) return -1
if (b.loc === `${SITE_ORIGIN}/`) return 1 if (b.loc === `${SITE_ORIGIN}/`) return 1
return a.loc.localeCompare(b.loc) 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"?>', '<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"', '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
' xmlns:xhtml="http://www.w3.org/1999/xhtml">',
] ]
for (const e of entries) { for (const e of entries) {
lines.push(' <url>') parts.push(
lines.push(` <loc>${escapeXml(e.loc)}</loc>`) ' <url>',
lines.push(` <lastmod>${escapeXml(e.lastmod)}</lastmod>`) ` <loc>${escapeXml(e.loc)}</loc>`,
lines.push(` <changefreq>${escapeXml(e.changefreq)}</changefreq>`) ` <lastmod>${escapeXml(e.lastmod)}</lastmod>`,
lines.push(` <priority>${escapeXml(e.priority)}</priority>`) ` <changefreq>${escapeXml(e.changefreq)}</changefreq>`,
// Self-referencing hreflang (single-language site) ` <priority>${escapeXml(e.priority)}</priority>`,
lines.push( ' </url>'
` <xhtml:link rel="alternate" hreflang="en" href="${escapeXml(e.loc)}" />`
) )
lines.push(
` <xhtml:link rel="alternate" hreflang="x-default" href="${escapeXml(e.loc)}" />`
)
lines.push(' </url>')
} }
lines.push('</urlset>') parts.push('</urlset>')
lines.push('') // Trailing newline for POSIX-friendly files
return lines.join('\n') return parts.join('\n') + '\n'
} }
function serveSitemap(req, res) { function serveSitemap(req, res) {
try { try {
const xml = buildSitemapXml() const xml = buildSitemapXml()
// text/xml is what Google documents for sitemaps; charset for clarity
const headers = { const headers = {
'Content-Type': 'application/xml; charset=utf-8', 'Content-Type': 'text/xml; charset=utf-8',
// Short cache so new pages appear soon after deploys
'Cache-Control': 'public, max-age=300', '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') { if (req.method === 'HEAD') {
res.writeHead(200, { res.writeHead(200, {
...headers, ...headers,
'Content-Length': Buffer.byteLength(xml), 'Content-Length': Buffer.byteLength(xml, 'utf8'),
}) })
res.end() res.end()
return return