Skip to content

Sitemaps

@scribe-atp/core exports getSitemapEntries — a function that takes a fetched Site and returns an array of { url, lastmod? } objects. You use those to build the XML yourself, giving you full control over the output format.

import { fetchSite, getSitemapEntries } from '@scribe-atp/core';
const site = await fetchSite('alice.bsky.social', 'alice-bsky-social');
const entries = getSitemapEntries(site, { baseUrl: 'https://alice.example.com' });
// entries is: Array<{ url: string; lastmod?: string }>
app/routes/sitemap.ts
import { fetchSite, getSitemapEntries } from '@scribe-atp/core';
export async function loader({ request }: { request: Request }) {
const origin = new URL(request.url).origin;
const site = await fetchSite('alice.bsky.social', 'alice-bsky-social', request.signal);
const entries = getSitemapEntries(site, { baseUrl: origin });
const xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...entries.map(
(e) =>
`<url><loc>${e.url}</loc>${e.lastmod ? `<lastmod>${e.lastmod}</lastmod>` : ''}</url>`
),
'</urlset>',
].join('');
return new Response(xml, {
headers: { 'Content-Type': 'application/xml; charset=utf-8' },
});
}

getSitemapEntries only returns URLs for published articles. To include static pages (home page, about, etc.), append them manually:

const entries = getSitemapEntries(site, { baseUrl: origin });
const staticPages = [
{ url: `${origin}/` },
{ url: `${origin}/about` },
];
const allEntries = [...staticPages, ...entries];

getSitemapEntries returns one entry per published article. Each entry includes:

  • url — the canonical article URL, built from the site’s url and urlPrefix
  • lastmod — the article’s updatedAt timestamp (ISO 8601 format), when set

Unpublished articles (site.ungroupedArticles) are excluded.