import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import { siteConfig } from "../config";
import { shouldShowPost, shouldShowContent, getPostSlug } from "../utils/markdown";
function shouldExcludeFromSitemap(slug: string): boolean {
const excludedSlugs = ["404", "sitemap", "rss"];
return excludedSlugs.includes(slug);
}
// Helper function to normalize siteUrl - ensure it ends with a single slash
function normalizeSiteUrl(url: string): string {
return url.replace(/\/+$/, '') + '/';
}
// Compute slug from collection entry id
function getSlugFromId(id: string): string {
// Remove file extension and handle folder-based content
// For example: "about/index" -> "about", "getting-started.md" -> "getting-started"
return id.replace(/\/index$/, "").replace(/\.mdx?$/, "");
}
export const GET: APIRoute = async () => {
const siteUrl = normalizeSiteUrl(import.meta.env.SITE || siteConfig.site);
// Get all content collections
const posts = await getCollection("posts");
const pages = await getCollection("pages");
const projects = await getCollection("projects");
const docs = await getCollection("docs");
// Filter posts based on environment (in dev, show all including drafts)
const isDev = import.meta.env.DEV;
const visiblePosts = posts.filter(
(post) => shouldShowPost(post, isDev) && !post.data.noIndex
);
// Filter pages (exclude drafts, special pages, and noIndex)
const visiblePages = pages.filter(
(page) =>
shouldShowContent(page, isDev) &&
!page.data.noIndex &&
!shouldExcludeFromSitemap(getSlugFromId(page.id))
);
// Filter projects and docs based on environment and optional content type settings
const visibleProjects = siteConfig.optionalContentTypes.projects
? projects.filter(
(project) => shouldShowContent(project, isDev) && !project.data.noIndex
)
: [];
const visibleDocs = siteConfig.optionalContentTypes.docs
? docs.filter((doc) => shouldShowContent(doc, isDev) && !doc.data.noIndex)
: [];
// Generate URLs
const urls: string[] = [];
// Homepage
urls.push(`
${siteUrl}
${new Date().toISOString()}
daily
1.0
`);
// Posts index page
urls.push(`
${siteUrl}posts/
${new Date().toISOString()}
daily
0.8
`);
// Projects index page (only if projects are enabled)
if (siteConfig.optionalContentTypes.projects) {
urls.push(`
${siteUrl}projects/
${new Date().toISOString()}
weekly
0.7
`);
}
// Documentation index page (only if docs are enabled)
if (siteConfig.optionalContentTypes.docs) {
urls.push(`
${siteUrl}docs/
${new Date().toISOString()}
weekly
0.7
`);
}
// Individual posts
visiblePosts.forEach((post) => {
const slug = getPostSlug(post);
urls.push(`
${siteUrl}posts/${slug}/
${post.data.date.toISOString()}
monthly
0.7
`);
});
// Individual pages
visiblePages.forEach((page) => {
const slug = getSlugFromId(page.id);
urls.push(`
${siteUrl}${slug}/
${page.data.lastModified || new Date().toISOString()}
monthly
0.8
`);
});
// Individual projects
visibleProjects.forEach((project) => {
const lastmod = project.data.date;
const slug = getSlugFromId(project.id);
urls.push(`
${siteUrl}projects/${slug}/
${lastmod.toISOString()}
monthly
0.7
`);
});
// Individual documentation pages
visibleDocs.forEach((doc) => {
const lastmod = doc.data.lastModified || new Date();
const slug = getSlugFromId(doc.id);
urls.push(`
${siteUrl}docs/${slug}/
${lastmod.toISOString()}
monthly
0.6
`);
});
// Posts pagination pages
const postsPerPage = siteConfig.postOptions.postsPerPage;
const totalPages = Math.ceil(visiblePosts.length / postsPerPage);
for (let page = 2; page <= totalPages; page++) {
urls.push(`
${siteUrl}posts/${page}/
${new Date().toISOString()}
weekly
0.5
`);
}
const sitemap = `
${urls.join("")}
`;
return new Response(sitemap, {
headers: {
"Content-Type": "application/xml",
"Cache-Control": "public, max-age=3600", // Cache for 1 hour
},
});
};