Skip to content

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 `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  ${routes.map((route) => `<url><loc>${route.url}</loc></url>`).join("\n  ")}
</urlset>`;
      },
    }),
  ],
});

Function Signature

typescript
type XmlSerializer = (routes: Route[]) => string | Promise<string>;

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) => `
  <url>
    <loc>${escapeXml(route.url)}</loc>
    ${route.lastmod ? `<lastmod>${route.lastmod}</lastmod>` : ""}
    ${route.changefreq ? `<changefreq>${route.changefreq}</changefreq>` : ""}
    ${route.priority ? `<priority>${route.priority}</priority>` : ""}
    <!-- Custom extension -->
    <custom:rating>${route.rating || 0}</custom:rating>
  </url>`,
      )
      .join("");

    return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:custom="http://example.com/custom">
${urls}
</urlset>`;
  },
});

XML Escaping

When building custom XML, properly escape special characters:

typescript
function escapeXml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;");
}

TIP

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

TIP

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 `<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by @pyyupsk/vite-plugin-sitemap -->
<!-- Last updated: ${now} -->
<!-- Total URLs: ${routes.length} -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${routes.map((r) => `  <url><loc>${escapeXml(r.url)}</loc></url>`).join("\n")}
</urlset>`;
  },
});

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

WARNING

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, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;");
}

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 = [`    <loc>${escapeXml(route.url)}</loc>`];

          if (route.lastmod) {
            elements.push(`    <lastmod>${route.lastmod}</lastmod>`);
          }
          if (route.changefreq) {
            elements.push(`    <changefreq>${route.changefreq}</changefreq>`);
          }
          if (route.priority !== undefined) {
            elements.push(`    <priority>${route.priority}</priority>`);
          }

          return `  <url>\n${elements.join("\n")}\n  </url>`;
        };

        return `<?xml version="1.0" encoding="UTF-8"?>
<!--
  Sitemap for example.com
  Generated: ${timestamp}
  Pages: ${sections.pages.length}
  Blog posts: ${sections.blog.length}
  Total: ${routes.length}
-->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <!-- Main pages -->
${sections.pages.map(buildUrl).join("\n")}

  <!-- Blog posts -->
${sections.blog.map(buildUrl).join("\n")}
</urlset>`;
      },
    }),
  ],
});

Released under the MIT License.