----- # Why This Plugin There are several sitemap plugins for Vite. Here's why `@pyyupsk/vite-plugin-sitemap` stands out. ## Key Differentiators ### Full Google Extensions Support Most sitemap plugins only support basic URL entries. This plugin provides first-class support for: - **Image sitemaps** - Help Google discover images on your pages - **Video sitemaps** - Provide rich metadata for video content - **News sitemaps** - Optimize for Google News indexing - **Internationalization** - hreflang annotations for multi-language sites ### Type-Safe Configuration Built with TypeScript from the ground up: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; // Full IntelliSense and compile-time checking const routes: Route[] = [ { url: "/video", videos: [ { title: "Tutorial", description: "Learn how to use our product", thumbnail_loc: "https://example.com/thumb.jpg", // TypeScript will catch missing required fields }, ], }, ]; ``` ### Runtime Validation Zod-powered validation catches errors before they reach production: ```bash $ vite-sitemap validate ✗ Validation failed: Route "/blog/post-1": - lastmod: Invalid W3C Datetime format Received: "2025-13-45" Suggestion: Use ISO 8601 format like "2025-01-15" or "2025-01-15T10:30:00Z" ``` ### Async Route Generation Fetch routes from any data source at build time: ```typescript export default async function getRoutes() { const posts = await fetch("https://api.example.com/posts").then((r) => r.json()); return posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, })); } ``` ### Automatic Sitemap Splitting Large sites with 50,000+ URLs are automatically split into multiple sitemaps with an index file, following Google's guidelines. ### Powerful CLI Validate, preview, and generate sitemaps without running a full Vite build: ```bash # Validate configuration vite-sitemap validate # Preview generated XML vite-sitemap preview # Generate without full build vite-sitemap generate --hostname https://example.com ``` ### Dev Mode Support Preview your sitemap during development without running a build. The plugin serves `/sitemap.xml` and `/robots.txt` dynamically: ```bash # Start dev server npm run dev # Access in browser http://localhost:5173/sitemap.xml http://localhost:5173/robots.txt ``` This allows you to verify your sitemap configuration in real-time as you develop. ## Comparison | Feature | vite-plugin-sitemap | Others | | -------------------- | ------------------- | ------- | | TypeScript | Full support | Partial | | Google Image Sitemap | Yes | No | | Google Video Sitemap | Yes | No | | Google News Sitemap | Yes | No | | hreflang Support | Yes | Partial | | Runtime Validation | Zod-powered | None | | Async Routes | Yes | Limited | | Auto-splitting | Yes | No | | CLI Tools | Full suite | None | | Dev Mode Support | Yes | No | | Zero Runtime | Yes | Yes | ## When to Use This Plugin This plugin is ideal if you: - Need Google extensions (images, videos, news) - Want comprehensive type safety - Have large sites requiring sitemap splitting - Need to validate sitemaps in CI/CD - Want to fetch routes from APIs at build time - Value detailed error messages and suggestions ## When to Consider Alternatives You might prefer a simpler solution if: - You only need basic sitemaps with no extensions - You're using a framework with built-in sitemap support (Next.js, Nuxt, etc.) - You prefer minimal configuration over flexibility ----- # Getting Started This guide will help you set up `@pyyupsk/vite-plugin-sitemap` in your Vite project. ## Prerequisites - Node.js 20 or higher - Vite 7.x ## Installation ```bash # npm npm install @pyyupsk/vite-plugin-sitemap # pnpm pnpm add @pyyupsk/vite-plugin-sitemap # yarn yarn add @pyyupsk/vite-plugin-sitemap # bun bun add @pyyupsk/vite-plugin-sitemap ``` ## Basic Setup ### 1. Configure the Plugin Add the plugin to your `vite.config.ts`: ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", }), ], }); ``` ### 2. Create a Sitemap File Create a `src/sitemap.ts` file to define your routes: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/", priority: 1.0, changefreq: "daily" }, { url: "/about", priority: 0.8 }, { url: "/blog", priority: 0.9, changefreq: "weekly" }, { url: "/contact", priority: 0.5 }, ] satisfies Route[]; ``` ### 3. Build Your Project Run your Vite build: ```bash npm run build ``` The plugin will generate `sitemap.xml` in your output directory (`dist/` by default). ## Generated Output After building, you'll find a `sitemap.xml` file like this: ```xml https://example.com/ daily 1.0 https://example.com/about 0.8 https://example.com/blog weekly 0.9 https://example.com/contact 0.5 ``` ## Adding robots.txt Enable automatic `robots.txt` generation: ```typescript sitemap({ hostname: "https://example.com", generateRobotsTxt: true, }); ``` This creates or updates `robots.txt` with a Sitemap directive: ```txt User-agent: * Allow: / Sitemap: https://example.com/sitemap.xml ``` ## Next Steps - Learn about Route Definition options - Explore Configuration options - Add Google Extensions for images, videos, and news - Use the CLI for validation and preview ----- # Configuration Configure the plugin in your `vite.config.ts` file. ## Basic Configuration ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", }), ], }); ``` ## All Options ```typescript sitemap({ // Base URL (required for relative URLs) hostname: "https://example.com", // Path to sitemap definition file sitemapFile: "src/sitemap", // Output directory (defaults to Vite's build.outDir) outDir: "dist", // Output filename filename: "sitemap.xml", // Default values for all routes changefreq: "weekly", priority: 0.5, lastmod: "2025-01-15", // URL patterns to exclude exclude: ["/admin/*", /^\/private/], // Transform routes before generation transform: (route) => route, // Custom XML serializer serialize: (routes) => "...", // Generate robots.txt generateRobotsTxt: true, }); ``` ## Options Reference ### hostname - **Type:** `string` - **Required:** Yes (for relative URLs) The base URL of your site. Prepended to relative URLs: ```typescript sitemap({ hostname: "https://example.com", }); // Route { url: "/about" } becomes "https://example.com/about" ``` ### sitemapFile - **Type:** `string` - **Default:** Auto-discovered Path to your sitemap definition file (without extension): ```typescript sitemap({ sitemapFile: "config/sitemap", // Loads config/sitemap.ts }); ``` If not specified, the plugin searches: 1. `src/sitemap.{ts,js,mts,mjs}` 2. `sitemap.{ts,js,mts,mjs}` (project root) ### outDir - **Type:** `string` - **Default:** Vite's `build.outDir` Output directory for generated files: ```typescript sitemap({ outDir: "public", // Output to public/ instead of dist/ }); ``` ### filename - **Type:** `string` - **Default:** `"sitemap.xml"` Name of the output sitemap file: ```typescript sitemap({ filename: "urlset.xml", }); ``` ### changefreq - **Type:** `ChangeFrequency` - **Default:** `undefined` Default change frequency applied to all routes: ```typescript sitemap({ changefreq: "weekly", }); ``` ### priority - **Type:** `number` (0.0 - 1.0) - **Default:** `undefined` Default priority applied to all routes: ```typescript sitemap({ priority: 0.5, }); ``` ### lastmod - **Type:** `string` (W3C Datetime) - **Default:** `undefined` Default last modified date applied to all routes: ```typescript sitemap({ lastmod: new Date().toISOString().split("T")[0], }); ``` ### exclude - **Type:** `Array` - **Default:** `[]` URL patterns to exclude from the sitemap: ```typescript sitemap({ exclude: [ // String patterns (glob-like) "/admin/*", "/api/*", "/internal/*", // Regular expressions /^\/private/, /\/draft-/, ], }); ``` ### transform - **Type:** `(route: Route) => Route | null | Promise` - **Default:** `undefined` Transform each route before XML generation. Return `null` to exclude: ```typescript sitemap({ transform: (route) => { // Add trailing slashes if (!route.url.endsWith("/")) { return { ...route, url: `${route.url}/` }; } return route; }, }); ``` ```typescript sitemap({ transform: async (route) => { // Fetch additional metadata const meta = await fetchPageMeta(route.url); // Exclude unpublished pages if (!meta.published) { return null; } return { ...route, lastmod: meta.updatedAt, }; }, }); ``` ### serialize - **Type:** `(routes: Route[]) => string | Promise` - **Default:** `undefined` Custom XML serialization function: ```typescript sitemap({ serialize: (routes) => { // Return custom XML return ` ${routes.map((r) => `${r.url}`).join("\n ")} `; }, }); ``` ### generateRobotsTxt - **Type:** `boolean` - **Default:** `false` Generate or update `robots.txt` with Sitemap directive: ```typescript sitemap({ hostname: "https://example.com", generateRobotsTxt: true, }); ``` Creates: ```txt User-agent: * Allow: / Sitemap: https://example.com/sitemap.xml ``` Or appends to existing `robots.txt`: ```txt # Your existing rules... Sitemap: https://example.com/sitemap.xml ``` ## TypeScript Configuration For full type safety, import the plugin types: ```typescript import type { PluginOptions } from "@pyyupsk/vite-plugin-sitemap"; import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; const sitemapOptions: PluginOptions = { hostname: "https://example.com", changefreq: "weekly", }; export default defineConfig({ plugins: [sitemap(sitemapOptions)], }); ``` ## Environment-Specific Configuration Use Vite's mode for different environments: ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig(({ mode }) => ({ plugins: [ sitemap({ hostname: mode === "production" ? "https://example.com" : "https://staging.example.com", }), ], })); ``` ----- # Route Definition Routes define the URLs in your sitemap. This guide covers all the ways to define routes. ## Basic Structure Create a `src/sitemap.ts` file (or `sitemap.ts` in your project root): ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [{ url: "/" }, { url: "/about" }, { url: "/contact" }] satisfies Route[]; ``` ## Route Properties Each route can have the following properties: ```typescript interface Route { // Required url: string; // Optional standard properties lastmod?: string; changefreq?: ChangeFrequency; priority?: number; // Optional Google extensions images?: Image[]; videos?: Video[]; news?: News; alternates?: Alternate[]; } ``` ### url (required) The URL of the page. Can be absolute or relative: ```typescript // Relative URLs (hostname prepended automatically) { url: "/" } { url: "/about" } { url: "/blog/post-1" } // Absolute URLs { url: "https://example.com/page" } ``` URL Requirements: - Maximum 2,048 characters - Must use `http://` or `https://` protocol (after hostname resolution) - Cannot contain URL fragments (`#`) ### lastmod The date the page was last modified. Must be in W3C Datetime format: ```typescript // Date only { url: "/", lastmod: "2025-01-15" } // With time and timezone { url: "/", lastmod: "2025-01-15T10:30:00Z" } { url: "/", lastmod: "2025-01-15T10:30:00+05:30" } // Dynamic using JavaScript { url: "/", lastmod: new Date().toISOString().split("T")[0] } ``` ### changefreq How frequently the page is likely to change: ```typescript type ChangeFrequency = | "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"; { url: "/", changefreq: "daily" } { url: "/about", changefreq: "monthly" } { url: "/archive/2020", changefreq: "never" } ``` ### priority The priority of this URL relative to other URLs on your site: ```typescript // Values from 0.0 to 1.0 { url: "/", priority: 1.0 } // Highest priority { url: "/about", priority: 0.8 } { url: "/contact", priority: 0.5 } // Default { url: "/terms", priority: 0.3 } // Lower priority ``` Priority is relative to other pages on your site, not absolute. A priority of `1.0` doesn't guarantee top search ranking. ## Async Route Generation For dynamic content, export an async function: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default async function getRoutes(): Promise { // Fetch from API const posts = await fetch("https://api.example.com/posts").then((r) => r.json()); // Fetch from database const products = await db.product.findMany(); return [ { url: "/", priority: 1.0 }, ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, changefreq: "weekly" as const, })), ...products.map((product) => ({ url: `/products/${product.id}`, lastmod: product.modifiedAt, })), ]; } ``` ## Multiple Sitemaps Use named exports to generate multiple sitemap files: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; // Generates sitemap-pages.xml export const pages: Route[] = [{ url: "/", priority: 1.0 }, { url: "/about" }, { url: "/contact" }]; // Generates sitemap-blog.xml export const blog: Route[] = [ { url: "/blog", priority: 0.9 }, { url: "/blog/post-1" }, { url: "/blog/post-2" }, ]; // Generates sitemap-products.xml (async) export async function products(): Promise { const items = await fetchProducts(); return items.map((p) => ({ url: `/products/${p.id}` })); } ``` This generates: - `sitemap-pages.xml` - `sitemap-blog.xml` - `sitemap-products.xml` - `sitemap-index.xml` (referencing all three) ## File Discovery The plugin searches for your sitemap file in this order: 1. Custom path (if `sitemapFile` option is set) 2. `src/sitemap.{ts,js,mts,mjs}` 3. `sitemap.{ts,js,mts,mjs}` (project root) ## Full Example ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; // Static pages export const pages: Route[] = [ { url: "/", priority: 1.0, changefreq: "daily", lastmod: "2025-01-15", }, { url: "/about", priority: 0.8, changefreq: "monthly", }, { url: "/gallery", priority: 0.7, images: [ { loc: "https://example.com/images/hero.jpg", title: "Hero Image", caption: "Our main hero image", }, ], }, ]; // Dynamic blog posts export async function blog(): Promise { const posts = await fetch("https://api.example.com/posts").then((r) => r.json()); return [ { url: "/blog", priority: 0.9, changefreq: "daily" }, ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, changefreq: "weekly" as const, priority: 0.6, })), ]; } ``` ----- # CLI The `vite-sitemap` CLI provides commands for validating, previewing, and generating sitemaps without running a full Vite build. ## Installation The CLI is included when you install the package: ```bash npm install @pyyupsk/vite-plugin-sitemap ``` Run with `npx`: ```bash npx vite-sitemap ``` Or add scripts to your `package.json`: ```json { "scripts": { "sitemap:validate": "vite-sitemap validate", "sitemap:preview": "vite-sitemap preview", "sitemap:generate": "vite-sitemap generate" } } ``` ## Commands ### validate Validate your sitemap configuration without generating files. ```bash vite-sitemap validate [options] ``` **Options:** | Option | Description | Default | | ---------------------- | -------------------------- | --------------- | | `-r, --root ` | Project root directory | `process.cwd()` | | `-s, --sitemap ` | Path to sitemap file | Auto-discovered | | `-h, --hostname ` | Hostname for relative URLs | From config | | `-v, --verbose` | Show detailed output | `false` | **Examples:** ```bash # Basic validation vite-sitemap validate # With custom root vite-sitemap validate --root ./my-project # With custom sitemap file vite-sitemap validate --sitemap config/sitemap.ts # Verbose output vite-sitemap validate --verbose ``` **Output:** ```bash $ vite-sitemap validate i Validating sitemap configuration... ✓ Validation passed! 42 routes validated in 156ms ``` On error: ```bash $ vite-sitemap validate i Validating sitemap configuration... ✗ Validation failed for default: Route "/blog/post-1": - lastmod: Invalid W3C Datetime format Received: "2025-13-45" Suggestion: Use format like "2025-01-15" or "2025-01-15T10:30:00Z" Route "/products/widget": - priority: Must be between 0.0 and 1.0 Received: 1.5 ``` ### preview Preview the generated XML without writing files. ```bash vite-sitemap preview [options] ``` **Options:** | Option | Description | Default | | ---------------------- | ----------------------------- | --------------- | | `-r, --root ` | Project root directory | `process.cwd()` | | `-s, --sitemap ` | Path to sitemap file | Auto-discovered | | `-h, --hostname ` | Hostname for URLs | From config | | `-n, --name ` | Preview specific named export | All | | `-l, --limit ` | Limit output lines | `50` | | `-f, --full` | Show full XML output | `false` | | `-v, --verbose` | Show detailed output | `false` | **Examples:** ```bash # Preview all sitemaps vite-sitemap preview # Preview specific export vite-sitemap preview --name blog # Full output (no truncation) vite-sitemap preview --full # Custom hostname vite-sitemap preview --hostname https://staging.example.com ``` **Output:** ```bash $ vite-sitemap preview i Loading sitemap configuration... i Preview: default (5 routes) ------------------------------------------------------------ https://example.com/ daily 1.0 https://example.com/about 0.8 ... ------------------------------------------------------------ Size: 1.2 KB Routes: 5 ✓ Preview complete in 89ms ``` ### generate Generate sitemap files without running a full Vite build. ```bash vite-sitemap generate [options] ``` **Options:** | Option | Description | Default | | ---------------------- | ---------------------- | --------------- | | `-r, --root ` | Project root directory | `process.cwd()` | | `-s, --sitemap ` | Path to sitemap file | Auto-discovered | | `-o, --output ` | Output directory | `dist` | | `-h, --hostname ` | Base hostname | From config | | `--robots-txt` | Generate robots.txt | From config | | `-v, --verbose` | Show detailed output | `false` | **Examples:** ```bash # Basic generation vite-sitemap generate --hostname https://example.com # Custom output directory vite-sitemap generate -h https://example.com -o public # With robots.txt vite-sitemap generate -h https://example.com --robots-txt # Verbose output vite-sitemap generate -h https://example.com --verbose ``` **Output:** ```bash $ vite-sitemap generate -h https://example.com --robots-txt --verbose i Generating sitemap... Working directory: /home/user/project Output directory: dist i sitemap.xml (42 URLs, 8.5 KB) ✓ Created robots.txt with Sitemap directive ------------------------------------------------------ ✓ built 1 sitemap(s) with 42 URLs in 234ms Generated files: -> dist/sitemap.xml -> dist/robots.txt ``` ## Global Options These options work with all commands: | Option | Description | | --------------------- | ------------------------ | | `-c, --config ` | Path to vite.config file | | `--verbose` | Enable verbose output | | `-V, --version` | Display version number | | `--help` | Display help for command | ## CI/CD Integration ### GitHub Actions ```yaml name: Validate Sitemap on: [push, pull_request] jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx vite-sitemap validate ``` ### Pre-commit Hook Using husky: ```bash # .husky/pre-commit npx vite-sitemap validate ``` ### Build Script ```json { "scripts": { "build": "vite build && vite-sitemap generate -h https://example.com", "prebuild": "vite-sitemap validate" } } ``` ## Config File Integration The CLI automatically reads options from your `vite.config.ts`: ```typescript // vite.config.ts import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", generateRobotsTxt: true, }), ], }); ``` CLI commands will use these options as defaults: ```bash # Uses hostname from vite.config.ts vite-sitemap validate # Override with CLI option vite-sitemap validate --hostname https://staging.example.com ``` ## Exit Codes | Code | Description | | ---- | --------------------------- | | `0` | Success | | `1` | Validation error or failure | Use exit codes in scripts: ```bash vite-sitemap validate && echo "Valid!" || echo "Invalid!" ``` ----- # Large Sitemaps When your site has thousands of URLs, the plugin automatically handles sitemap splitting according to Google's guidelines. ## Automatic Splitting The plugin automatically splits sitemaps when they exceed: - **50,000 URLs** per sitemap file - **45 MB** file size (with 5 MB buffer from Google's 50 MB limit) No configuration required - it just works. ## How It Works When splitting is triggered: 1. Routes are distributed across multiple sitemap files 2. Each file is named sequentially: `sitemap-0.xml`, `sitemap-1.xml`, etc. 3. A `sitemap-index.xml` is generated referencing all sitemap files ## Example Output For a site with 120,000 URLs: ``` dist/ ├── sitemap-0.xml # URLs 1-50,000 ├── sitemap-1.xml # URLs 50,001-100,000 ├── sitemap-2.xml # URLs 100,001-120,000 └── sitemap-index.xml # References all sitemaps ``` ### Generated Index File ```xml https://example.com/sitemap-0.xml 2025-01-15 https://example.com/sitemap-1.xml 2025-01-15 https://example.com/sitemap-2.xml 2025-01-15 ``` ## Named Exports with Splitting When using named exports, each export can be split independently: ```typescript // Large product catalog export async function products(): Promise { // Returns 75,000 routes return await fetchAllProducts(); } // Large blog archive export async function blog(): Promise { // Returns 60,000 routes return await fetchAllPosts(); } ``` **Output:** ``` dist/ ├── sitemap-products-0.xml # Products 1-50,000 ├── sitemap-products-1.xml # Products 50,001-75,000 ├── sitemap-blog-0.xml # Posts 1-50,000 ├── sitemap-blog-1.xml # Posts 50,001-60,000 └── sitemap-index.xml # References all 4 sitemaps ``` ## Performance Considerations ### Memory Usage For extremely large sitemaps, consider: 1. **Streaming data** - Don't load all routes into memory at once 2. **Pagination** - Fetch routes in batches ```typescript export default async function getRoutes(): Promise { const routes: Route[] = []; let page = 1; const pageSize = 10000; while (true) { const batch = await fetchRoutes({ page, pageSize }); if (batch.length === 0) break; routes.push(...batch); page++; } return routes; } ``` ### Build Time Large sitemaps increase build time. To optimize: 1. **Cache API responses** - Store fetched data locally during development 2. **Parallel fetching** - Use `Promise.all` for independent data sources ```typescript export default async function getRoutes(): Promise { // Parallel fetching const [products, posts, pages] = await Promise.all([fetchProducts(), fetchPosts(), fetchPages()]); return [...products, ...posts, ...pages]; } ``` ## robots.txt with Split Sitemaps When `generateRobotsTxt` is enabled with split sitemaps, the Sitemap directive points to the index file: ```txt User-agent: * Allow: / Sitemap: https://example.com/sitemap-index.xml ``` ## Monitoring Sitemap Size Use the CLI to check sitemap sizes without building: ```bash vite-sitemap preview --verbose ``` Output shows size information: ```bash i Preview: default (75,000 routes) Size: 12.4 MB Routes: 75,000 ! Sitemap will be split into 2 files (exceeds 50,000 URL limit) ``` ## Google Search Console After deploying split sitemaps: 1. Submit `sitemap-index.xml` to Google Search Console 2. Google will automatically discover all child sitemaps 3. Monitor indexing status for each sitemap file ## Best Practices 1. **Use the index file** - Always submit the index, not individual sitemaps 2. **Keep URLs consistent** - Don't change URL structures between builds 3. **Update incrementally** - Only regenerate sitemaps when content changes 4. **Monitor coverage** - Check Search Console for indexing issues ----- # Dynamic Routes Generate sitemap routes dynamically from APIs, databases, CMSs, or any async data source at build time. ## Basic Async Routes Export an async function from your sitemap file: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default async function getRoutes(): Promise { const posts = await fetch("https://api.example.com/posts").then((r) => r.json()); return [ { url: "/", priority: 1.0 }, { url: "/blog", priority: 0.9 }, ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, })), ]; } ``` ## Multiple Data Sources Combine data from multiple sources: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default async function getRoutes(): Promise { // Fetch from multiple APIs in parallel const [posts, products, categories] = await Promise.all([ fetch("https://api.example.com/posts").then((r) => r.json()), fetch("https://api.example.com/products").then((r) => r.json()), fetch("https://api.example.com/categories").then((r) => r.json()), ]); return [ // Static pages { url: "/", priority: 1.0 }, { url: "/about", priority: 0.8 }, // Blog posts ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, changefreq: "weekly" as const, })), // Products ...products.map((product) => ({ url: `/products/${product.id}`, lastmod: product.modifiedAt, images: product.images.map((img) => ({ loc: img.url })), })), // Categories ...categories.map((category) => ({ url: `/category/${category.slug}`, changefreq: "daily" as const, })), ]; } ``` ## Database Integration ### Prisma ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export default async function getRoutes(): Promise { const [posts, products] = await Promise.all([ prisma.post.findMany({ where: { published: true }, select: { slug: true, updatedAt: true }, }), prisma.product.findMany({ where: { active: true }, select: { id: true, updatedAt: true }, }), ]); await prisma.$disconnect(); return [ { url: "/", priority: 1.0 }, ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt.toISOString(), })), ...products.map((product) => ({ url: `/products/${product.id}`, lastmod: product.updatedAt.toISOString(), })), ]; } ``` ### Drizzle ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; import { db } from "./db"; import { posts, products } from "./schema"; import { eq } from "drizzle-orm"; export default async function getRoutes(): Promise { const [allPosts, allProducts] = await Promise.all([ db.select().from(posts).where(eq(posts.published, true)), db.select().from(products).where(eq(products.active, true)), ]); return [ { url: "/" }, ...allPosts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, })), ...allProducts.map((product) => ({ url: `/products/${product.id}`, lastmod: product.updatedAt, })), ]; } ``` ## CMS Integration ### Contentful ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; import { createClient } from "contentful"; const client = createClient({ space: process.env.CONTENTFUL_SPACE_ID!, accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!, }); export default async function getRoutes(): Promise { const entries = await client.getEntries({ content_type: "blogPost", limit: 1000, }); return [ { url: "/", priority: 1.0 }, { url: "/blog", priority: 0.9 }, ...entries.items.map((entry) => ({ url: `/blog/${entry.fields.slug}`, lastmod: entry.sys.updatedAt, })), ]; } ``` ### Sanity ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; import { createClient } from "@sanity/client"; const client = createClient({ projectId: process.env.SANITY_PROJECT_ID!, dataset: "production", apiVersion: "2024-01-01", useCdn: true, }); export default async function getRoutes(): Promise { const posts = await client.fetch(` *[_type == "post" && defined(slug.current)] { "slug": slug.current, _updatedAt } `); return [ { url: "/" }, ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post._updatedAt, })), ]; } ``` ### Strapi ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; const STRAPI_URL = process.env.STRAPI_URL || "http://localhost:1337"; export default async function getRoutes(): Promise { const response = await fetch(`${STRAPI_URL}/api/posts?populate=*`); const { data } = await response.json(); return [ { url: "/" }, ...data.map((post) => ({ url: `/blog/${post.attributes.slug}`, lastmod: post.attributes.updatedAt, })), ]; } ``` ## Named Exports with Async Each named export can be a separate async function: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; // Static pages export const pages: Route[] = [{ url: "/", priority: 1.0 }, { url: "/about" }, { url: "/contact" }]; // Dynamic blog posts export async function blog(): Promise { const posts = await fetchBlogPosts(); return posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, })); } // Dynamic products export async function products(): Promise { const products = await fetchProducts(); return products.map((product) => ({ url: `/products/${product.id}`, lastmod: product.modifiedAt, })); } ``` ## Error Handling Handle API failures gracefully: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default async function getRoutes(): Promise { const staticRoutes: Route[] = [{ url: "/", priority: 1.0 }, { url: "/about" }]; try { const posts = await fetch("https://api.example.com/posts").then((r) => { if (!r.ok) throw new Error(`API returned ${r.status}`); return r.json(); }); return [ ...staticRoutes, ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, })), ]; } catch (error) { console.error("Failed to fetch posts:", error); // Return static routes only on failure return staticRoutes; } } ``` ## Caching Cache API responses during development: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; import { readFile, writeFile } from "node:fs/promises"; import { existsSync } from "node:fs"; const CACHE_FILE = ".sitemap-cache.json"; const CACHE_TTL = 1000 * 60 * 5; // 5 minutes async function getCachedOrFetch(key: string, fetcher: () => Promise): Promise { if (process.env.NODE_ENV === "development" && existsSync(CACHE_FILE)) { const cache = JSON.parse(await readFile(CACHE_FILE, "utf-8")); if (cache[key] && Date.now() - cache[key].timestamp < CACHE_TTL) { return cache[key].data; } } const data = await fetcher(); if (process.env.NODE_ENV === "development") { const cache = existsSync(CACHE_FILE) ? JSON.parse(await readFile(CACHE_FILE, "utf-8")) : {}; cache[key] = { data, timestamp: Date.now() }; await writeFile(CACHE_FILE, JSON.stringify(cache, null, 2)); } return data; } export default async function getRoutes(): Promise { const posts = await getCachedOrFetch("posts", () => fetch("https://api.example.com/posts").then((r) => r.json()), ); return [{ url: "/" }, ...posts.map((post) => ({ url: `/blog/${post.slug}` }))]; } ``` ## Environment Variables Use environment variables for API keys and URLs: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; const API_URL = process.env.API_URL || "https://api.example.com"; const API_KEY = process.env.API_KEY; export default async function getRoutes(): Promise { const response = await fetch(`${API_URL}/posts`, { headers: { Authorization: `Bearer ${API_KEY}`, }, }); const posts = await response.json(); return posts.map((post) => ({ url: `/blog/${post.slug}` })); } ``` ----- # Custom Serialization Override the default XML generation with your own serialization logic. ## Basic Usage Use the `serialize` option to provide a custom XML serializer: ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", serialize: (routes) => { // Return custom XML string return ` ${routes.map((route) => `${route.url}`).join("\n ")} `; }, }), ], }); ``` ## Function Signature ```typescript type XmlSerializer = (routes: Route[]) => string | Promise; ``` The function receives an array of validated routes and must return the complete XML string. ## Using Built-in Helpers Import helper functions for building XML elements: ```typescript import sitemap, { buildSitemapXml } from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", serialize: (routes) => { // Use the built-in helper for consistent output return buildSitemapXml(routes); }, }), ], }); ``` ## Async Serialization The serializer can be async: ```typescript sitemap({ hostname: "https://example.com", serialize: async (routes) => { // Fetch additional data const metadata = await fetchSiteMetadata(); // Generate XML with additional context return generateCustomXml(routes, metadata); }, }); ``` ## Adding Custom Elements Add custom XML elements not supported by default: ```typescript sitemap({ hostname: "https://example.com", serialize: (routes) => { const urls = routes .map( (route) => ` ${escapeXml(route.url)} ${route.lastmod ? `${route.lastmod}` : ""} ${route.changefreq ? `${route.changefreq}` : ""} ${route.priority ? `${route.priority}` : ""} ${route.rating || 0} `, ) .join(""); return ` ${urls} `; }, }); ``` ## XML Escaping When building custom XML, properly escape special characters: ```typescript function escapeXml(str: string): string { return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } ``` The built-in XML builders (`buildSitemapXml`, `buildUrlElement`) automatically handle XML escaping. ## Filtering Routes Filter routes during serialization: ```typescript sitemap({ hostname: "https://example.com", serialize: (routes) => { // Only include high-priority routes const filteredRoutes = routes.filter((r) => (r.priority || 0.5) >= 0.7); return buildSitemapXml(filteredRoutes); }, }); ``` Prefer using the `transform` option or `exclude` patterns for filtering. They run before validation and are more efficient. ## Sorting Routes Sort routes in the output: ```typescript sitemap({ hostname: "https://example.com", serialize: (routes) => { // Sort by priority (highest first) const sorted = [...routes].sort((a, b) => (b.priority || 0.5) - (a.priority || 0.5)); return buildSitemapXml(sorted); }, }); ``` ## Adding Comments Add XML comments for documentation: ```typescript sitemap({ hostname: "https://example.com", serialize: (routes) => { const now = new Date().toISOString(); return ` ${routes.map((r) => ` ${escapeXml(r.url)}`).join("\n")} `; }, }); ``` ## Alternative Output Formats Generate non-XML formats (though not standard sitemap format): ```typescript // JSON output (for debugging) sitemap({ hostname: "https://example.com", filename: "sitemap.json", serialize: (routes) => JSON.stringify(routes, null, 2), }); // Plain text URL list sitemap({ hostname: "https://example.com", filename: "urls.txt", serialize: (routes) => routes.map((r) => r.url).join("\n"), }); ``` Non-XML formats won't be recognized by search engines as valid sitemaps. ## Full Example ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; // Local escape function for custom XML function escapeXml(str: string): string { return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", serialize: (routes) => { const timestamp = new Date().toISOString(); // Group routes by section const sections = { pages: routes.filter((r) => !r.url.includes("/blog/")), blog: routes.filter((r) => r.url.includes("/blog/")), }; const buildUrl = (route) => { const elements = [` ${escapeXml(route.url)}`]; if (route.lastmod) { elements.push(` ${route.lastmod}`); } if (route.changefreq) { elements.push(` ${route.changefreq}`); } if (route.priority !== undefined) { elements.push(` ${route.priority}`); } return ` \n${elements.join("\n")}\n `; }; return ` ${sections.pages.map(buildUrl).join("\n")} ${sections.blog.map(buildUrl).join("\n")} `; }, }), ], }); ``` ----- # Image Sitemaps Image sitemaps help Google discover images on your pages. This is especially useful for JavaScript-rendered images that might not be discovered during crawling. ## Basic Usage Add an `images` array to any route: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/gallery", images: [ { loc: "https://example.com/images/photo1.jpg", }, { loc: "https://example.com/images/photo2.jpg", }, ], }, ] satisfies Route[]; ``` ## Image Properties ```typescript interface Image { // Required loc: string; // Optional title?: string; caption?: string; geo_location?: string; license?: string; } ``` ### loc (required) The URL of the image. Must be an absolute URL: ```typescript { loc: "https://example.com/images/hero.jpg" } ``` ### title The title of the image: ```typescript { loc: "https://example.com/images/hero.jpg", title: "Beautiful Mountain Landscape" } ``` ### caption A caption describing the image: ```typescript { loc: "https://example.com/images/hero.jpg", title: "Mountain Landscape", caption: "A stunning view of the Swiss Alps at sunset" } ``` ### geo_location The geographic location where the image was taken: ```typescript { loc: "https://example.com/images/hero.jpg", geo_location: "Swiss Alps, Switzerland" } ``` ### license A URL to the license of the image: ```typescript { loc: "https://example.com/images/hero.jpg", license: "https://example.com/image-license" } ``` ## Full Example ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/gallery", priority: 0.8, changefreq: "weekly", images: [ { loc: "https://example.com/images/landscape.jpg", title: "Mountain Landscape", caption: "A stunning view of the Swiss Alps at sunset", geo_location: "Swiss Alps, Switzerland", license: "https://example.com/license", }, { loc: "https://example.com/images/city.jpg", title: "City Skyline", caption: "Downtown skyline at night", geo_location: "New York, USA", }, ], }, { url: "/products/widget", images: [ { loc: "https://example.com/products/widget-front.jpg", title: "Widget - Front View", }, { loc: "https://example.com/products/widget-side.jpg", title: "Widget - Side View", }, ], }, ] satisfies Route[]; ``` ## Generated XML The above configuration generates: ```xml https://example.com/gallery weekly 0.8 https://example.com/images/landscape.jpg Mountain Landscape A stunning view of the Swiss Alps at sunset Swiss Alps, Switzerland https://example.com/license https://example.com/images/city.jpg City Skyline Downtown skyline at night New York, USA ``` ## Dynamic Image Generation Fetch images from an API or CMS: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default async function getRoutes(): Promise { const galleries = await fetch("https://api.example.com/galleries").then((r) => r.json()); return galleries.map((gallery) => ({ url: `/gallery/${gallery.slug}`, lastmod: gallery.updatedAt, images: gallery.photos.map((photo) => ({ loc: photo.url, title: photo.title, caption: photo.description, geo_location: photo.location, })), })); } ``` ## Best Practices 1. **Use descriptive titles and captions** - Help search engines understand your images 2. **Include geo_location** - Useful for location-based image searches 3. **Maximum 1,000 images per URL** - Google's limit per page 4. **Use absolute URLs** - All image URLs must be absolute ----- # Video Sitemaps Video sitemaps provide rich metadata about video content on your pages, helping Google understand and index your videos. ## Basic Usage Add a `videos` array to any route: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/tutorials/getting-started", videos: [ { title: "Getting Started Tutorial", description: "Learn how to set up and use our product", thumbnail_loc: "https://example.com/thumbnails/tutorial.jpg", content_loc: "https://example.com/videos/tutorial.mp4", }, ], }, ] satisfies Route[]; ``` ## Video Properties ```typescript interface Video { // Required title: string; description: string; thumbnail_loc: string; // Location (at least one required) content_loc?: string; player_loc?: string; // Optional duration?: number; expiration_date?: string; rating?: number; view_count?: number; publication_date?: string; family_friendly?: boolean; restriction?: VideoRestriction; platform?: VideoPlatform; requires_subscription?: boolean; uploader?: VideoUploader; live?: boolean; tag?: string[]; } ``` ### Required Properties #### title The title of the video (max 100 characters): ```typescript { title: "Getting Started with Our Product" } ``` #### description A description of the video (max 2,048 characters): ```typescript { description: "In this comprehensive guide, you'll learn how to set up and configure our product for your specific use case." } ``` #### thumbnail_loc URL to the video thumbnail image: ```typescript { thumbnail_loc: "https://example.com/thumbnails/video.jpg" } ``` ### Location Properties At least one of `content_loc` or `player_loc` is required. #### content_loc Direct URL to the video file: ```typescript { content_loc: "https://example.com/videos/tutorial.mp4" } ``` #### player_loc URL to a player for the video: ```typescript { player_loc: "https://example.com/player?video=tutorial" } ``` ### Optional Properties #### duration Video duration in seconds (1 to 28,800): ```typescript { duration: 300 } // 5 minutes ``` #### rating Video rating from 0.0 to 5.0: ```typescript { rating: 4.5 } ``` #### view_count Number of times the video has been viewed: ```typescript { view_count: 15000 } ``` #### publication_date When the video was first published (W3C Datetime): ```typescript { publication_date: "2025-01-15" } ``` #### expiration_date When the video will no longer be available: ```typescript { expiration_date: "2025-12-31" } ``` #### family_friendly Whether the video is suitable for all ages: ```typescript { family_friendly: true } ``` #### live Whether the video is a live stream: ```typescript { live: false } ``` #### requires_subscription Whether a subscription is required to view: ```typescript { requires_subscription: false } ``` #### tag Tags associated with the video (max 32 tags): ```typescript { tag: ["tutorial", "beginner", "guide", "setup"] } ``` ### Complex Properties #### restriction Geographic restrictions: ```typescript { restriction: { relationship: "allow", countries: ["US", "CA", "GB"] } } // Or deny specific countries { restriction: { relationship: "deny", countries: ["CN", "RU"] } } ``` #### platform Platform restrictions: ```typescript { platform: { relationship: "allow", platforms: ["web", "mobile"] // "web" | "mobile" | "tv" } } ``` #### uploader Information about the video uploader: ```typescript { uploader: { name: "Example Channel", info: "https://example.com/channel" } } ``` ## Full Example ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/videos/tutorial", priority: 0.9, videos: [ { title: "Getting Started Tutorial", description: "Learn how to use our product in this comprehensive guide covering setup, configuration, and best practices.", thumbnail_loc: "https://example.com/thumbnails/tutorial.jpg", content_loc: "https://example.com/videos/tutorial.mp4", duration: 600, rating: 4.8, view_count: 25000, publication_date: "2025-01-15", family_friendly: true, tag: ["tutorial", "beginner", "guide"], uploader: { name: "Example Tutorials", info: "https://example.com/channel", }, restriction: { relationship: "allow", countries: ["US", "CA", "GB", "AU"], }, platform: { relationship: "allow", platforms: ["web", "mobile", "tv"], }, }, ], }, ] satisfies Route[]; ``` ## Generated XML ```xml https://example.com/videos/tutorial 0.9 https://example.com/thumbnails/tutorial.jpg Getting Started Tutorial Learn how to use our product... https://example.com/videos/tutorial.mp4 600 4.8 25000 2025-01-15 yes tutorial beginner guide US CA GB AU web mobile tv Example Tutorials ``` ## Dynamic Video Generation ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default async function getRoutes(): Promise { const videos = await fetch("https://api.example.com/videos").then((r) => r.json()); return videos.map((video) => ({ url: `/videos/${video.slug}`, lastmod: video.updatedAt, videos: [ { title: video.title, description: video.description, thumbnail_loc: video.thumbnailUrl, content_loc: video.videoUrl, duration: video.durationSeconds, publication_date: video.publishedAt, view_count: video.views, tag: video.tags, }, ], })); } ``` ## Best Practices 1. **Use high-quality thumbnails** - Minimum 160x90, recommended 1920x1080 2. **Write accurate descriptions** - Help users understand video content 3. **Keep titles concise** - Maximum 100 characters 4. **Update view counts** - Rebuild sitemap periodically to update metrics 5. **Set expiration dates** - For time-limited content ----- # News Sitemaps News sitemaps help Google News discover and index your news articles. They're essential for news publishers who want their articles to appear in Google News. ## Basic Usage Add a `news` object to any route: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/news/breaking-story", news: { publication: { name: "Example News", language: "en", }, publication_date: "2025-01-15T10:30:00Z", title: "Breaking: Important Announcement", }, }, ] satisfies Route[]; ``` ## News Properties ```typescript interface News { // Required publication: NewsPublication; publication_date: string; title: string; // Optional keywords?: string; stock_tickers?: string; } interface NewsPublication { name: string; language: string; } ``` ### Required Properties #### publication Information about the news publication: ```typescript { publication: { name: "Example News", // Publication name language: "en" // ISO 639-1 language code } } ``` #### publication_date When the article was published (W3C Datetime): ```typescript { publication_date: "2025-01-15T10:30:00Z" } // Or just the date { publication_date: "2025-01-15" } ``` #### title The title of the news article (max 2,048 characters): ```typescript { title: "Breaking: Major Tech Company Announces New Product" } ``` ### Optional Properties #### keywords Comma-separated list of keywords: ```typescript { keywords: "technology, business, announcement, product launch" } ``` #### stock_tickers Comma-separated stock tickers (max 5): ```typescript { stock_tickers: "NASDAQ:GOOG, NASDAQ:AAPL" } ``` ## Full Example ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; const now = new Date().toISOString(); export default [ { url: "/news/tech-announcement", lastmod: now, news: { publication: { name: "Tech Daily", language: "en", }, publication_date: now, title: "Major Tech Company Unveils Revolutionary New Product", keywords: "technology, innovation, product launch, silicon valley", stock_tickers: "NASDAQ:TECH", }, }, { url: "/news/market-update", lastmod: now, news: { publication: { name: "Tech Daily", language: "en", }, publication_date: now, title: "Stock Markets React to Federal Reserve Decision", keywords: "finance, stocks, federal reserve, economy", stock_tickers: "NYSE:DJI, NASDAQ:IXIC, NYSE:SPX", }, }, ] satisfies Route[]; ``` ## Generated XML ```xml https://example.com/news/tech-announcement 2025-01-15T10:30:00.000Z Tech Daily en 2025-01-15T10:30:00.000Z Major Tech Company Unveils Revolutionary New Product technology, innovation, product launch, silicon valley NASDAQ:TECH ``` ## Dynamic News Generation ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default async function getRoutes(): Promise { // Fetch recent articles (last 2 days for Google News) const articles = await fetch("https://api.example.com/articles?since=2d").then((r) => r.json()); return articles.map((article) => ({ url: `/news/${article.slug}`, lastmod: article.updatedAt, news: { publication: { name: "Example News", language: article.language, }, publication_date: article.publishedAt, title: article.title, keywords: article.tags.join(", "), }, })); } ``` ## Multi-Language News For publications in multiple languages: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; // English articles export const newsEn: Route[] = [ { url: "/en/news/article-1", news: { publication: { name: "Global News", language: "en", }, publication_date: "2025-01-15", title: "English Article Title", }, }, ]; // Spanish articles export const newsEs: Route[] = [ { url: "/es/noticias/articulo-1", news: { publication: { name: "Global News", language: "es", }, publication_date: "2025-01-15", title: "Titulo del Articulo en Espanol", }, }, ]; ``` ## Best Practices 1. **Fresh content only** - Google News indexes articles from the past 2 days 2. **Accurate publication dates** - Must match when the article was first published 3. **Original content** - Don't include aggregated or syndicated content 4. **Unique titles** - Each article should have a distinct, descriptive title 5. **Update frequently** - Rebuild sitemap when new articles are published 6. **Use ISO 639-1 codes** - For language (en, es, fr, de, etc.) ## Google News Requirements To be included in Google News: 1. Your site must be accepted into Google News 2. Articles must be original news content 3. Publication must have a consistent name 4. Articles should be timely (within 2 days) ----- # Internationalization (hreflang) Use hreflang annotations to tell Google about alternate language versions of your pages. This helps serve the correct language version to users in different regions. ## Basic Usage Add an `alternates` array to any route: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/products", alternates: [ { hreflang: "en", href: "https://example.com/products" }, { hreflang: "es", href: "https://example.com/es/productos" }, { hreflang: "fr", href: "https://example.com/fr/produits" }, ], }, ] satisfies Route[]; ``` ## Alternate Properties ```typescript interface Alternate { hreflang: string; href: string; } ``` ### hreflang Language and optional region code: ```typescript // Language only (ISO 639-1) { hreflang: "en" } { hreflang: "es" } { hreflang: "fr" } // Language and region (ISO 639-1 + ISO 3166-1 Alpha 2) { hreflang: "en-US" } { hreflang: "en-GB" } { hreflang: "es-MX" } { hreflang: "zh-TW" } // Default fallback { hreflang: "x-default" } ``` ### href The URL of the alternate version (must be absolute): ```typescript { href: "https://example.com/es/productos" } ``` ## Full Example ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; const languages = ["en", "es", "fr", "de", "ja"] as const; export default [ // English version { url: "https://example.com/products", alternates: [ { hreflang: "en", href: "https://example.com/products" }, { hreflang: "es", href: "https://example.com/es/productos" }, { hreflang: "fr", href: "https://example.com/fr/produits" }, { hreflang: "de", href: "https://example.com/de/produkte" }, { hreflang: "ja", href: "https://example.com/ja/products" }, { hreflang: "x-default", href: "https://example.com/products" }, ], }, // Spanish version { url: "https://example.com/es/productos", alternates: [ { hreflang: "en", href: "https://example.com/products" }, { hreflang: "es", href: "https://example.com/es/productos" }, { hreflang: "fr", href: "https://example.com/fr/produits" }, { hreflang: "de", href: "https://example.com/de/produkte" }, { hreflang: "ja", href: "https://example.com/ja/products" }, { hreflang: "x-default", href: "https://example.com/products" }, ], }, // ... other language versions ] satisfies Route[]; ``` ## Generated XML ```xml https://example.com/products ``` ## Dynamic Multi-Language Routes Generate routes for all language versions automatically: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; const hostname = "https://example.com"; const languages = [ { code: "en", prefix: "" }, { code: "es", prefix: "/es" }, { code: "fr", prefix: "/fr" }, { code: "de", prefix: "/de" }, ] as const; const pages = [ { path: "/products", translations: { es: "/productos", fr: "/produits", de: "/produkte" } }, { path: "/about", translations: { es: "/acerca", fr: "/a-propos", de: "/uber-uns" } }, { path: "/contact", translations: { es: "/contacto", fr: "/contact", de: "/kontakt" } }, ]; function getLocalizedPath(page: (typeof pages)[0], lang: (typeof languages)[0]): string { if (lang.code === "en") return page.path; return page.translations[lang.code as keyof typeof page.translations] || page.path; } function generateAlternates(page: (typeof pages)[0]) { return [ ...languages.map((lang) => ({ hreflang: lang.code, href: `${hostname}${lang.prefix}${getLocalizedPath(page, lang)}`, })), { hreflang: "x-default", href: `${hostname}${page.path}` }, ]; } export default languages.flatMap((lang) => pages.map((page) => ({ url: `${lang.prefix}${getLocalizedPath(page, lang)}`, alternates: generateAlternates(page), })), ) satisfies Route[]; ``` ## Region-Specific Variants For content that varies by region within the same language: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/pricing", alternates: [ // US English (default) { hreflang: "en-US", href: "https://example.com/pricing" }, // UK English (different prices/currency) { hreflang: "en-GB", href: "https://example.co.uk/pricing" }, // Australian English { hreflang: "en-AU", href: "https://example.com.au/pricing" }, // Canadian English { hreflang: "en-CA", href: "https://example.ca/pricing" }, // Canadian French { hreflang: "fr-CA", href: "https://example.ca/fr/tarification" }, // Default fallback { hreflang: "x-default", href: "https://example.com/pricing" }, ], }, ] satisfies Route[]; ``` ## Using with CMS Data ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; interface Page { slug: string; locale: string; translations: Array<{ locale: string; slug: string }>; } export default async function getRoutes(): Promise { const pages: Page[] = await fetch("https://cms.example.com/pages").then((r) => r.json()); return pages.map((page) => ({ url: `/${page.locale}/${page.slug}`, alternates: [ { hreflang: page.locale, href: `https://example.com/${page.locale}/${page.slug}` }, ...page.translations.map((t) => ({ hreflang: t.locale, href: `https://example.com/${t.locale}/${t.slug}`, })), { hreflang: "x-default", href: `https://example.com/en/${page.slug}` }, ], })); } ``` ## Best Practices 1. **Include all alternates** - Every language version should list all other versions 2. **Self-reference** - Include the current page in its own alternates list 3. **Use x-default** - Provide a fallback for users whose language isn't listed 4. **Consistent URLs** - Use the same URL structure across languages 5. **Bidirectional links** - If page A links to page B, page B must link back to A 6. **Absolute URLs** - All href values must be absolute URLs ## Common Language Codes | Code | Language | | ---- | ---------- | | `en` | English | | `es` | Spanish | | `fr` | French | | `de` | German | | `it` | Italian | | `pt` | Portuguese | | `ja` | Japanese | | `zh` | Chinese | | `ko` | Korean | | `ar` | Arabic | ## Common Region Codes | Code | Region | | ------- | ------------------------ | | `en-US` | English (United States) | | `en-GB` | English (United Kingdom) | | `es-ES` | Spanish (Spain) | | `es-MX` | Spanish (Mexico) | | `pt-BR` | Portuguese (Brazil) | | `zh-CN` | Chinese (Simplified) | | `zh-TW` | Chinese (Traditional) | ----- # Plugin Options Complete reference for all plugin configuration options. ## Usage ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ // options here }), ], }); ``` ## Options ### hostname - **Type:** `string` - **Default:** `undefined` The base URL of your site. Required for relative URLs to be properly resolved. ```typescript sitemap({ hostname: "https://example.com", }); ``` All relative URLs in your routes will be prefixed with this hostname: ```typescript // Route: { url: "/about" } // Output: https://example.com/about ``` ### sitemapFile - **Type:** `string` - **Default:** Auto-discovered Path to your sitemap definition file (without extension). ```typescript sitemap({ sitemapFile: "config/sitemap", }); ``` If not specified, the plugin searches in order: 1. `src/sitemap.{ts,js,mts,mjs}` 2. `sitemap.{ts,js,mts,mjs}` (project root) ### outDir - **Type:** `string` - **Default:** Vite's `build.outDir` Output directory for generated sitemap files. ```typescript sitemap({ outDir: "public", }); ``` ### filename - **Type:** `string` - **Default:** `"sitemap.xml"` Name of the output sitemap file. ```typescript sitemap({ filename: "urlset.xml", }); ``` When sitemap splitting occurs, this becomes the base name: `sitemap-0.xml`, `sitemap-1.xml`, etc. ### changefreq - **Type:** `ChangeFrequency` - **Default:** `undefined` Default change frequency applied to all routes that don't specify their own. ```typescript sitemap({ changefreq: "weekly", }); ``` **Valid values:** - `"always"` - Changes every time accessed - `"hourly"` - Changes every hour - `"daily"` - Changes every day - `"weekly"` - Changes every week - `"monthly"` - Changes every month - `"yearly"` - Changes every year - `"never"` - Archived content ### priority - **Type:** `number` - **Default:** `undefined` Default priority applied to all routes. Must be between `0.0` and `1.0`. ```typescript sitemap({ priority: 0.5, }); ``` Priority is relative to other pages on your site, not absolute. The default for pages without a specified priority is `0.5`. ### lastmod - **Type:** `string` - **Default:** `undefined` Default last modified date for all routes. Must be in W3C Datetime format. ```typescript sitemap({ lastmod: "2025-01-15", }); // Or dynamic sitemap({ lastmod: new Date().toISOString().split("T")[0], }); ``` **Valid formats:** - `"2025"` - Year only - `"2025-01"` - Year and month - `"2025-01-15"` - Full date - `"2025-01-15T10:30:00Z"` - With time (UTC) - `"2025-01-15T10:30:00+05:30"` - With timezone ### exclude - **Type:** `Array` - **Default:** `[]` URL patterns to exclude from the sitemap. ```typescript sitemap({ exclude: [ // String patterns (glob-like matching) "/admin/*", "/api/*", "/internal/*", // Regular expressions /^\/private/, /\/draft-/, /\?/, // Exclude URLs with query strings ], }); ``` ### transform - **Type:** `(route: Route) => Route | null | Promise` - **Default:** `undefined` Transform each route before XML generation. Return `null` to exclude the route. ```typescript // Add trailing slashes sitemap({ transform: (route) => ({ ...route, url: route.url.endsWith("/") ? route.url : `${route.url}/`, }), }); // Async transformation sitemap({ transform: async (route) => { const meta = await fetchPageMeta(route.url); if (!meta.published) return null; return { ...route, lastmod: meta.updatedAt }; }, }); // Exclude specific routes sitemap({ transform: (route) => { if (route.url.includes("/secret/")) return null; return route; }, }); ``` ### serialize - **Type:** `(routes: Route[]) => string | Promise` - **Default:** `undefined` Custom XML serialization function. Receives all validated routes and must return the complete XML string. ```typescript sitemap({ serialize: (routes) => { return ` ${routes.map((r) => `${r.url}`).join("\n ")} `; }, }); ``` ### generateRobotsTxt - **Type:** `boolean` - **Default:** `false` Generate or update `robots.txt` with a Sitemap directive. ```typescript sitemap({ hostname: "https://example.com", generateRobotsTxt: true, }); ``` **Creates new robots.txt:** ```txt User-agent: * Allow: / Sitemap: https://example.com/sitemap.xml ``` **Updates existing robots.txt:** - Appends Sitemap directive if not present - Preserves existing content Requires `hostname` to be set for the Sitemap URL. ## TypeScript For full type safety, import the options type: ```typescript import type { PluginOptions } from "@pyyupsk/vite-plugin-sitemap"; import sitemap from "@pyyupsk/vite-plugin-sitemap"; const options: PluginOptions = { hostname: "https://example.com", changefreq: "weekly", }; export default defineConfig({ plugins: [sitemap(options)], }); ``` ## Complete Example ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", sitemapFile: "src/sitemap", outDir: "dist", filename: "sitemap.xml", changefreq: "weekly", priority: 0.5, lastmod: new Date().toISOString().split("T")[0], exclude: ["/admin/*", "/api/*", /^\/private/], transform: (route) => { // Add trailing slash if (!route.url.endsWith("/")) { return { ...route, url: `${route.url}/` }; } return route; }, generateRobotsTxt: true, }), ], }); ``` ----- # Route Options Complete reference for route configuration options. ## Basic Route ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; const route: Route = { url: "/about", }; ``` ## Route Interface ```typescript interface Route { // Required url: string; // Standard sitemap properties lastmod?: string; changefreq?: ChangeFrequency; priority?: number; // Google extensions images?: Image[]; videos?: Video[]; news?: News; alternates?: Alternate[]; } ``` ## Standard Properties ### url - **Type:** `string` - **Required:** Yes The URL of the page. Can be relative or absolute. ```typescript // Relative (hostname prepended from config) { url: "/about" } { url: "/blog/post-1" } // Absolute { url: "https://example.com/about" } ``` **Requirements:** - Maximum 2,048 characters - Must be valid URL format - No URL fragments (`#`) - After resolution, must use `http://` or `https://` ### lastmod - **Type:** `string` - **Required:** No The date the page was last modified. Must be W3C Datetime format. ```typescript { url: "/", lastmod: "2025-01-15" } { url: "/", lastmod: "2025-01-15T10:30:00Z" } { url: "/", lastmod: "2025-01-15T10:30:00+05:30" } ``` **Valid formats:** | Format | Example | |--------|---------| | Year | `"2025"` | | Year-Month | `"2025-01"` | | Date | `"2025-01-15"` | | DateTime (UTC) | `"2025-01-15T10:30:00Z"` | | DateTime (offset) | `"2025-01-15T10:30:00+05:30"` | ### changefreq - **Type:** `ChangeFrequency` - **Required:** No How frequently the page is likely to change. ```typescript type ChangeFrequency = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"; ``` ```typescript { url: "/", changefreq: "daily" } { url: "/blog", changefreq: "weekly" } { url: "/archive/2020", changefreq: "never" } ``` ### priority - **Type:** `number` - **Required:** No The priority of this URL relative to other URLs on your site. ```typescript { url: "/", priority: 1.0 } // Highest { url: "/about", priority: 0.8 } { url: "/contact", priority: 0.5 } // Default { url: "/terms", priority: 0.3 } // Lower ``` **Requirements:** - Value between `0.0` and `1.0` - Default is `0.5` if not specified ## Google Extensions ### images - **Type:** `Image[]` - **Required:** No Images associated with this URL. ```typescript interface Image { loc: string; // Required: Image URL title?: string; // Image title caption?: string; // Image caption geo_location?: string; // Geographic location license?: string; // License URL } ``` ```typescript { url: "/gallery", images: [ { loc: "https://example.com/images/photo.jpg", title: "Beautiful Photo", caption: "A stunning landscape", geo_location: "Swiss Alps", }, ], } ``` ### videos - **Type:** `Video[]` - **Required:** No Videos associated with this URL. ```typescript interface Video { // Required title: string; description: string; thumbnail_loc: string; // At least one required content_loc?: string; player_loc?: string; // Optional duration?: number; expiration_date?: string; rating?: number; view_count?: number; publication_date?: string; family_friendly?: boolean; restriction?: VideoRestriction; platform?: VideoPlatform; requires_subscription?: boolean; uploader?: VideoUploader; live?: boolean; tag?: string[]; } ``` ```typescript { url: "/videos/tutorial", videos: [ { title: "Getting Started", description: "Learn the basics", thumbnail_loc: "https://example.com/thumb.jpg", content_loc: "https://example.com/video.mp4", duration: 300, }, ], } ``` ### news - **Type:** `News` - **Required:** No News article metadata. ```typescript interface News { publication: { name: string; // Publication name language: string; // ISO 639-1 code }; publication_date: string; // W3C Datetime title: string; // Article title // Optional keywords?: string; // Comma-separated stock_tickers?: string; // Max 5 tickers } ``` ```typescript { url: "/news/breaking", news: { publication: { name: "Example News", language: "en", }, publication_date: "2025-01-15T10:30:00Z", title: "Breaking News Story", keywords: "breaking, news, important", }, } ``` ### alternates - **Type:** `Alternate[]` - **Required:** No Alternate language versions. ```typescript interface Alternate { hreflang: string; // Language code or "x-default" href: string; // Absolute URL } ``` ```typescript { url: "/products", alternates: [ { hreflang: "en", href: "https://example.com/products" }, { hreflang: "es", href: "https://example.com/es/productos" }, { hreflang: "x-default", href: "https://example.com/products" }, ], } ``` ## Complete Example ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ // Basic page { url: "/", priority: 1.0, changefreq: "daily", lastmod: "2025-01-15", }, // Page with images { url: "/gallery", priority: 0.8, images: [ { loc: "https://example.com/images/hero.jpg", title: "Hero Image", }, ], }, // Video page { url: "/tutorials", videos: [ { title: "Tutorial", description: "Learn how to use our product", thumbnail_loc: "https://example.com/thumb.jpg", content_loc: "https://example.com/video.mp4", duration: 600, }, ], }, // News article { url: "/news/announcement", news: { publication: { name: "Company Blog", language: "en" }, publication_date: new Date().toISOString(), title: "Important Announcement", }, }, // Multi-language page { url: "/about", alternates: [ { hreflang: "en", href: "https://example.com/about" }, { hreflang: "es", href: "https://example.com/es/acerca" }, { hreflang: "x-default", href: "https://example.com/about" }, ], }, ] satisfies Route[]; ``` ----- # API Reference This page documents the public API exports from `@pyyupsk/vite-plugin-sitemap`. ## Plugin ### sitemapPlugin (default) The main Vite plugin function. ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; // Usage sitemap(options?: PluginOptions): Plugin ``` **Parameters:** - `options` - Plugin options **Returns:** Vite Plugin object **Example:** ```typescript import sitemap from "@pyyupsk/vite-plugin-sitemap"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ sitemap({ hostname: "https://example.com", }), ], }); ``` ## Generator Functions ### generateSitemap Generate sitemap XML from routes programmatically. ```typescript import { generateSitemap } from "@pyyupsk/vite-plugin-sitemap"; async function generateSitemap( routes: Route[], options?: GeneratorOptions, ): Promise; ``` **Parameters:** - `routes` - Array of route objects - `options` - Generator options **Returns:** `GeneratorResult` object with XML and metadata **Example:** ```typescript import { generateSitemap } from "@pyyupsk/vite-plugin-sitemap"; const result = await generateSitemap( [{ url: "https://example.com/" }, { url: "https://example.com/about" }], { hostname: "https://example.com" }, ); if (result.success) { console.log(result.xml); } ``` ### validateRoutes Validate routes without generating XML. ```typescript import { validateRoutes } from "@pyyupsk/vite-plugin-sitemap"; function validateRoutes(routes: Route[]): ValidationResult; ``` **Parameters:** - `routes` - Array of route objects **Returns:** `ValidationResult` with validation status and errors **Example:** ```typescript import { validateRoutes } from "@pyyupsk/vite-plugin-sitemap"; const result = validateRoutes([ { url: "https://example.com/" }, { url: "invalid-url" }, // Will fail validation ]); if (!result.valid) { console.error(result.errors); } ``` ## XML Builders ### buildSitemapXml Build a complete sitemap XML string from routes. ```typescript import { buildSitemapXml } from "@pyyupsk/vite-plugin-sitemap"; function buildSitemapXml(routes: Route[]): string; ``` **Example:** ```typescript import { buildSitemapXml } from "@pyyupsk/vite-plugin-sitemap"; const xml = buildSitemapXml([ { url: "https://example.com/" }, { url: "https://example.com/about" }, ]); ``` ### buildSitemapIndexXml Build a sitemap index XML string. ```typescript import { buildSitemapIndexXml } from "@pyyupsk/vite-plugin-sitemap"; function buildSitemapIndexXml(references: SitemapReference[]): string; ``` **Example:** ```typescript import { buildSitemapIndexXml } from "@pyyupsk/vite-plugin-sitemap"; const xml = buildSitemapIndexXml([ { loc: "https://example.com/sitemap-0.xml", lastmod: "2025-01-15" }, { loc: "https://example.com/sitemap-1.xml", lastmod: "2025-01-15" }, ]); ``` ### buildUrlElement Build a single URL element XML string. ```typescript import { buildUrlElement } from "@pyyupsk/vite-plugin-sitemap"; function buildUrlElement(route: Route): string; ``` **Example:** ```typescript import { buildUrlElement } from "@pyyupsk/vite-plugin-sitemap"; const xml = buildUrlElement({ url: "https://example.com/", lastmod: "2025-01-15", changefreq: "daily", priority: 1.0, }); // Output: // // https://example.com/ // 2025-01-15 // daily // 1.0 // ``` ## Utilities ### calculateByteSize Calculate the byte size of an XML string (UTF-8). ```typescript import { calculateByteSize } from "@pyyupsk/vite-plugin-sitemap"; function calculateByteSize(xml: string): number; ``` **Example:** ```typescript import { buildSitemapXml, calculateByteSize } from "@pyyupsk/vite-plugin-sitemap"; const xml = buildSitemapXml([{ url: "https://example.com/" }]); const size = calculateByteSize(xml); console.log(`Sitemap size: ${size} bytes`); ``` ## Validation ### ValidationError Error class for validation failures. ```typescript import { ValidationError } from "@pyyupsk/vite-plugin-sitemap"; interface ValidationError { field: string; message: string; suggestion?: string; received?: unknown; } ``` ### formatResultForConsole Format a validation result for console output. ```typescript import { formatResultForConsole } from "@pyyupsk/vite-plugin-sitemap"; function formatResultForConsole(result: ValidationResult): string; ``` ### formatErrorsForConsole Format validation errors for console output. ```typescript import { formatErrorsForConsole } from "@pyyupsk/vite-plugin-sitemap"; function formatErrorsForConsole(errors: ValidationError[]): string; ``` ## Full Export List ```typescript // Default export export { sitemapPlugin as default }; // Plugin export { sitemapPlugin, PLUGIN_NAME }; // Generator export { generateSitemap, validateRoutes }; export type { GenerationOptions, GenerationResult }; // XML Builders export { buildSitemapXml, buildSitemapIndexXml, buildUrlElement, calculateByteSize }; // Validation export { formatResultForConsole, formatErrorsForConsole }; export type { ValidationError, ValidationResult }; // Types export type { Route, ChangeFrequency, Image, Video, News, NewsPublication, Alternate, VideoRestriction, VideoPlatform, VideoUploader, PluginOptions, RouteTransformer, XmlSerializer, }; ``` ----- # Types TypeScript type definitions exported by `@pyyupsk/vite-plugin-sitemap`. ## Core Types ### Route A single URL entry in the sitemap. ```typescript interface Route { url: string; lastmod?: string; changefreq?: ChangeFrequency; priority?: number; images?: Image[]; videos?: Video[]; news?: News; alternates?: Alternate[]; } ``` ### ChangeFrequency Valid change frequency values. ```typescript type ChangeFrequency = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"; ``` ### RouteGenerator Async function that returns routes. ```typescript type RouteGenerator = () => Promise | Route[]; ``` ### SitemapDefaultExport Valid default export types from sitemap.ts. ```typescript type SitemapDefaultExport = Route[] | RouteGenerator; ``` ## Plugin Types ### PluginOptions Plugin configuration options. ```typescript interface PluginOptions { hostname?: string; sitemapFile?: string; outDir?: string; filename?: string; changefreq?: ChangeFrequency; priority?: number; lastmod?: string; exclude?: Array; transform?: RouteTransformer; serialize?: XmlSerializer; generateRobotsTxt?: boolean; } ``` ### RouteTransformer Function to transform routes before XML generation. ```typescript type RouteTransformer = (route: Route) => Route | null | Promise; ``` ### XmlSerializer Custom XML serializer function. ```typescript type XmlSerializer = (routes: Route[]) => string | Promise; ``` ## Extension Types ### Image Image metadata for Google Image sitemap extension. ```typescript interface Image { loc: string; title?: string; caption?: string; geo_location?: string; license?: string; } ``` ### Video Video metadata for Google Video sitemap extension. ```typescript interface Video { title: string; description: string; thumbnail_loc: string; content_loc?: string; player_loc?: string; duration?: number; expiration_date?: string; rating?: number; view_count?: number; publication_date?: string; family_friendly?: boolean; restriction?: VideoRestriction; platform?: VideoPlatform; requires_subscription?: boolean; uploader?: VideoUploader; live?: boolean; tag?: string[]; } ``` ### VideoRestriction Geographic restrictions for video playback. ```typescript interface VideoRestriction { relationship: "allow" | "deny"; countries: string[]; } ``` ### VideoPlatform Platform restrictions for video playback. ```typescript interface VideoPlatform { relationship: "allow" | "deny"; platforms: Array<"web" | "mobile" | "tv">; } ``` ### VideoUploader Information about the video uploader. ```typescript interface VideoUploader { name: string; info?: string; } ``` ### News News article metadata for Google News sitemap extension. ```typescript interface News { publication: NewsPublication; publication_date: string; title: string; keywords?: string; stock_tickers?: string; } ``` ### NewsPublication Information about the news publication. ```typescript interface NewsPublication { name: string; language: string; } ``` ### Alternate Alternate language version for hreflang annotations. ```typescript interface Alternate { hreflang: string; href: string; } ``` ## Result Types ### ValidationResult Result from route validation. ```typescript interface ValidationResult { valid: boolean; errors: ValidationError[]; warnings: ValidationWarning[]; } ``` ### ValidationError A validation error. ```typescript interface ValidationError { url: string; field: string; message: string; suggestion?: string; received?: unknown; } ``` ### ValidationWarning A validation warning (non-blocking). ```typescript interface ValidationWarning { url: string; field: string; message: string; } ``` ### GeneratorResult Result from sitemap generation. ```typescript interface GeneratorResult { success: boolean; xml?: string; byteSize?: number; routeCount?: number; warnings: string[]; validation: ValidationResult; splitResult?: SplitResult; } ``` ### SplitResult Result when sitemap was split into multiple files. ```typescript interface SplitResult { wasSplit: boolean; sitemaps: SitemapChunk[]; indexXml?: string; } ``` ### SitemapChunk A chunk of a split sitemap. ```typescript interface SitemapChunk { filename: string; xml: string; routes: Route[]; byteSize: number; } ``` ## Sitemap Types ### Sitemap Generated sitemap file structure. ```typescript interface Sitemap { filename: string; routes: Route[]; byteSize: number; } ``` ### SitemapIndex Index file referencing multiple sitemaps. ```typescript interface SitemapIndex { filename: string; sitemaps: SitemapReference[]; } ``` ### SitemapReference Reference to a child sitemap in an index. ```typescript interface SitemapReference { loc: string; lastmod?: string; } ``` ## Importing Types Import types for use in your code: ```typescript import type { Route, ChangeFrequency, Image, Video, News, Alternate, PluginOptions, RouteTransformer, XmlSerializer, ValidationResult, } from "@pyyupsk/vite-plugin-sitemap"; ``` ## Type-Safe Routes Use `satisfies` for type checking without explicit annotation: ```typescript import type { Route } from "@pyyupsk/vite-plugin-sitemap"; export default [ { url: "/", priority: 1.0, changefreq: "daily", }, { url: "/about", priority: 0.8, }, ] satisfies Route[]; ```