58 lines
2.8 KiB
JavaScript
58 lines
2.8 KiB
JavaScript
/**
|
|
* Serializes sitemap entries to a clean, indented XML STRING — valid for crawlers
|
|
* and (with `cssUrl`) styled in the browser via plain CSS.
|
|
*
|
|
* Two viewing modes:
|
|
* - No cssUrl → the browser's native formatted XML tree (indented, collapsible).
|
|
* - With cssUrl → a `<?xml-stylesheet type="text/css">` directive; the project's
|
|
* CSS styles the XML tags (cards, labels via ::before). NOT XSLT — that's being
|
|
* removed from browsers and shows a deprecation warning. CSS is safe and
|
|
* W3C-standard.
|
|
*
|
|
* // app/sitemap.xml/route.ts
|
|
* import { buildSitemapXml } from '@intecion/ipal-kit'
|
|
* import { sitemap } from '@/lib/content'
|
|
* export const dynamic = 'force-dynamic'
|
|
* export async function GET() {
|
|
* const entries = await sitemap()
|
|
* const xml = buildSitemapXml(entries, { cssUrl: '/sitemap.css' })
|
|
* return new Response(xml, {
|
|
* headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
|
* })
|
|
* }
|
|
*
|
|
* Put sitemap.css in the project's /public and style the tags (see docs/seo.md
|
|
* for a starter). Note: <loc> is an XML tag, not <a href> — CSS can't make it a
|
|
* clickable link (some browsers auto-detect URLs); the win is readability, not
|
|
* clickability.
|
|
*
|
|
* NOTE: if you use this custom route, DON'T also keep app/sitemap.ts — pick one.
|
|
* Two sitemaps confuse crawlers.
|
|
*/ export function buildSitemapXml(entries, opts = {}) {
|
|
const { cssUrl } = opts;
|
|
const esc = (s)=>s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
const urls = entries.map((e)=>{
|
|
const parts = [
|
|
` <loc>${esc(e.url)}</loc>`
|
|
];
|
|
if (e.lastModified) {
|
|
const iso = e.lastModified instanceof Date ? e.lastModified.toISOString() : String(e.lastModified);
|
|
parts.push(` <lastmod>${esc(iso)}</lastmod>`);
|
|
}
|
|
if (e.changeFrequency) parts.push(` <changefreq>${e.changeFrequency}</changefreq>`);
|
|
if (typeof e.priority === 'number') parts.push(` <priority>${e.priority}</priority>`);
|
|
const alternates = e.alternates?.languages;
|
|
if (alternates) {
|
|
for (const [lang, href] of Object.entries(alternates)){
|
|
if (typeof href === 'string') {
|
|
parts.push(` <xhtml:link rel="alternate" hreflang="${esc(lang)}" href="${esc(href)}"/>`);
|
|
}
|
|
}
|
|
}
|
|
return ` <url>\n${parts.join('\n')}\n </url>`;
|
|
}).join('\n');
|
|
const stylesheet = cssUrl ? `<?xml-stylesheet type="text/css" href="${esc(cssUrl)}"?>\n` : '';
|
|
return `<?xml version="1.0" encoding="UTF-8"?>\n` + stylesheet + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ` + `xmlns:xhtml="http://www.w3.org/1999/xhtml">\n` + urls + `\n</urlset>`;
|
|
}
|
|
|
|
//# sourceMappingURL=buildSitemapXml.js.map
|