Sitemap Generation

Automatically generate sitemap.xml from your Trident content.

Next.js App Router

Use the built-in Next.js sitemap convention with our helper:

// app/sitemap.ts
import { trident } from "@/lib/trident";
import { generateSitemapEntries } from "@trident/client/nextjs";

export default async function sitemap() {
  const entries = await trident.getSitemap();

  // Generate entries with your base URL
  const articleEntries = generateSitemapEntries(entries, {
    baseUrl: "https://yourdomain.com",
    urlPattern: "/blog/:slug", // Default: "/blog/:slug"
  });

  // Add your static pages
  const staticPages = [
    { url: "https://yourdomain.com", lastModified: new Date() },
    { url: "https://yourdomain.com/about", lastModified: new Date() },
    { url: "https://yourdomain.com/contact", lastModified: new Date() },
  ];

  return [...staticPages, ...articleEntries];
}

// Revalidate every hour
export const revalidate = 3600;

Custom URL Patterns

Customize the URL pattern for your articles:

// Different URL patterns
generateSitemapEntries(entries, {
  baseUrl: "https://yourdomain.com",
  urlPattern: "/blog/:slug",        // /blog/my-article
});

generateSitemapEntries(entries, {
  baseUrl: "https://yourdomain.com",
  urlPattern: "/articles/:slug",    // /articles/my-article
});

generateSitemapEntries(entries, {
  baseUrl: "https://yourdomain.com",
  urlPattern: "/:productSlug/:slug", // /my-product/my-article
});

generateSitemapEntries(entries, {
  baseUrl: "https://yourdomain.com",
  urlPattern: "/:languageCode/blog/:slug", // /en/blog/my-article
});

Available placeholders

PlaceholderValue
:slugArticle slug
:productSlugProduct slug
:languageCodeLanguage code (e.g., "en")
:territoryCodeTerritory code (e.g., "US")

XML Route Handler

For more control, use the route handler factory to serve sitemap.xml directly:

// app/sitemap.xml/route.ts
import { trident } from "@/lib/trident";
import { createSitemapRouteHandler } from "@trident/client/nextjs";

export const { GET } = createSitemapRouteHandler({
  client: trident,
  baseUrl: "https://yourdomain.com",
  urlPattern: "/blog/:slug",
  // Optional: add static entries
  staticEntries: [
    { loc: "https://yourdomain.com", lastmod: new Date().toISOString() },
    { loc: "https://yourdomain.com/about", lastmod: new Date().toISOString() },
  ],
});

// Revalidate every hour
export const revalidate = 3600;

Sitemap Index Entries

Build sitemap index entries when you split large sitemaps:

import {
  buildSitemapIndexEntries,
  generateSitemapIndexXml,
} from "@trident/client/nextjs";

const indexEntries = buildSitemapIndexEntries(entries, {
  baseUrl: "https://yourdomain.com",
  maxUrlsPerSitemap: 50000,
});

const xml = generateSitemapIndexXml(indexEntries);

Generate XML Manually

For non-Next.js frameworks, generate XML manually:

import { generateSitemapXml } from "@trident/client/nextjs";

// Fetch entries from Trident
const entries = await trident.getSitemap();

// Generate sitemap entries
const sitemapEntries = generateSitemapEntries(entries, {
  baseUrl: "https://yourdomain.com",
  urlPattern: "/blog/:slug",
});

// Generate XML string
const xml = generateSitemapXml(sitemapEntries);

// Serve as XML response
return new Response(xml, {
  headers: {
    "Content-Type": "application/xml",
    "Cache-Control": "public, max-age=3600",
  },
});

Multi-Territory Sitemaps

Handle multiple territories with hreflang annotations:

// app/sitemap.ts
import { trident } from "@/lib/trident";

export default async function sitemap() {
  const entries = await trident.getSitemap();

  // Group entries by slug to find alternate versions
  const bySlug = entries.reduce((acc, entry) => {
    const baseSlug = entry.slug.replace(/-[a-z]{2}(-[a-z]{2})?$/, "");
    if (!acc[baseSlug]) acc[baseSlug] = [];
    acc[baseSlug].push(entry);
    return acc;
  }, {} as Record<string, typeof entries>);

  return Object.entries(bySlug).flatMap(([baseSlug, versions]) => {
    return versions.map((entry) => ({
      url: `https://yourdomain.com/${entry.languageCode}/blog/${entry.slug}`,
      lastModified: entry.updatedAt,
      alternates: {
        languages: Object.fromEntries(
          versions.map((v) => [
            v.languageCode,
            `https://yourdomain.com/${v.languageCode}/blog/${v.slug}`,
          ])
        ),
      },
    }));
  });
}

Static Generation

Use sitemap entries to generate static pages at build time:

// app/blog/[slug]/page.tsx
import { trident } from "@/lib/trident";

export async function generateStaticParams() {
  const entries = await trident.getSitemap();

  return entries.map((entry) => ({
    slug: entry.slug,
  }));
}

export default async function ArticlePage({ params }) {
  const article = await trident.getArticle(params.slug);
  // ...
}