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 }, }); };