/** * /sitemap.xml.ts — Custom sitemap with per-type priority/changefreq. * * Generates a sitemap by: * 1. Listing all static .astro pages (excluding 404 + dynamic [slug]) * 2. Walking content collections (blog) for dynamic routes * 3. Classifying each URL into a category to assign priority + changefreq * * Per Astro 6, the file lives in src/pages/ and exports a GET handler. */ import type { APIRoute } from 'astro'; import { getCollection } from 'astro:content'; const SITE = 'https://dealplustech.com'; // --------------------------------------------------------------------------- // URL classification // --------------------------------------------------------------------------- const NOINDEX_PAGES = new Set([ 'privacy-policy', 'terms-and-conditions', '404', ]); const PRODUCT_SLUGS = new Set([ 'aeroflex', 'armflex', 'durgo-avvs', 'grilles', 'maxflex', 'pipe-coupling', 'realflex', 'water-pump', 'water-treatment', 'ตู้ดับเพลิง', 'ท่อ-hdpe', 'ท่อ-ppr-scg', 'ท่อ-ppr-thai-ppr', 'ท่อ-syler', 'ท่อ-upvc', 'ท่อ-xy-lent', 'ระบบรั้วไวน์แมน', 'รั้วเทวดา', 'วาล์ว-valve', 'หัวจ่าย-ball-jet', 'เครื่องเชื่อม-hdpe', 'เครื่องเชื่อม-ppr', 'เทอร์โมเบรค-thermobreak', 'เม็กกรู๊ฟ-คับปลิ้ง', ]); const HOMEPAGE_SLUGS = new Set(['index', 'all-products']); const CATEGORY_SLUGS = new Set(['ระบบน้ำ']); const STATIC_SLUGS = new Set(['about-us', 'contact-us', 'portfolio']); type Entry = { loc: string; priority: number; changefreq: 'daily' | 'weekly' | 'monthly' | 'yearly'; lastmod?: string; }; function classify(slug: string): { priority: number; changefreq: Entry['changefreq'] } { if (slug === 'index') return { priority: 1.0, changefreq: 'weekly' }; if (slug === 'all-products') return { priority: 0.9, changefreq: 'weekly' }; if (PRODUCT_SLUGS.has(slug)) return { priority: 0.9, changefreq: 'monthly' }; if (CATEGORY_SLUGS.has(slug)) return { priority: 0.8, changefreq: 'monthly' }; if (STATIC_SLUGS.has(slug)) return { priority: 0.6, changefreq: 'yearly' }; return { priority: 0.5, changefreq: 'monthly' }; } function urlFromSlug(slug: string): string { if (slug === 'index') return `${SITE}/`; return `${SITE}/${slug}`; } // --------------------------------------------------------------------------- // Build entries // --------------------------------------------------------------------------- async function buildEntries(): Promise { // 1. Static pages — discovered via Vite's import.meta.glob const modules = import.meta.glob('./*.astro'); const pageFiles = Object.keys(modules) .map(p => p.replace(/^\.\//, '').replace(/\.astro$/, '')) .filter(slug => !slug.startsWith('[') && !NOINDEX_PAGES.has(slug)); const entries: Entry[] = pageFiles.map(slug => { const { priority, changefreq } = classify(slug); return { loc: urlFromSlug(slug), priority, changefreq }; }); // 2. Blog posts (dynamic [slug] route) const articles = await getCollection('blog'); for (const article of articles) { entries.push({ loc: `${SITE}/${encodeURI('บทความ')}/${encodeURIComponent(article.id)}`, priority: 0.7, changefreq: 'yearly', lastmod: article.data.published_at.toISOString(), }); } // 3. Blog index entries.push({ loc: `${SITE}/${encodeURI('บทความ')}`, priority: 0.7, changefreq: 'weekly', }); // Sort: homepage first, then by priority desc, then by URL asc entries.sort((a, b) => { if (a.loc === `${SITE}/`) return -1; if (b.loc === `${SITE}/`) return 1; if (a.priority !== b.priority) return b.priority - a.priority; return a.loc.localeCompare(b.loc); }); return entries; } // --------------------------------------------------------------------------- // XML serialiser // --------------------------------------------------------------------------- function escapeXml(s: string): string { return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function toXml(entries: Entry[]): string { const urls = entries.map(e => { const lastmod = e.lastmod ? `\n ${e.lastmod}` : ''; return ` ${escapeXml(e.loc)}${lastmod} ${e.changefreq} ${e.priority.toFixed(1)} `; }).join('\n'); return ` ${urls} `; } // --------------------------------------------------------------------------- // Handler // --------------------------------------------------------------------------- export const GET: APIRoute = async () => { const entries = await buildEntries(); const xml = toXml(entries); return new Response(xml, { status: 200, headers: { 'Content-Type': 'application/xml; charset=utf-8', 'Cache-Control': 'public, max-age=3600', }, }); };