docs
    site-kit: Sitemap
    v7.2.0.md

    Sitemap — @sonordev/site-kit/sitemap

    Auto-generates sitemap.xml from your Next.js app directory structure. Discovers pages, resolves dynamic routes, syncs to Sonor, and optionally writes build-time llms.txt.

    Usage

    // app/sitemap.ts
    import { createSitemap } from '@sonordev/site-kit/sitemap'
    
    export default createSitemap({
      baseUrl: 'https://example.com',
    })

    Config

    interface SitemapConfig {
      baseUrl?: string                          // Resolved from Sonor API if not set
      trailingSlash?: boolean                   // Default: next.config's trailingSlash (see below)
      exclude?: string[]                        // Glob patterns: ['/admin/*', '/api/*']
      defaultPriority?: number                  // Default: 0.5
      defaultChangeFrequency?: 'weekly' | 'monthly' | 'yearly' | 'never'
    
      // Dynamic route resolution
      dynamicRoutes?: Record<string, string[]>  // Manual: { '[slug]': ['seo', 'analytics'] }
      resolveGenerateStaticParams?: boolean     // Auto-import generateStaticParams (default: true)
    
      // Priority overrides by path pattern
      priorities?: Record<string, number>       // { '/services/*': 0.8, '/article/*': 0.6 }
    
      // Additional paths not in app directory
      additionalPaths?: () => Promise<{ path: string; priority?: number }[]>
    
      // Sonor sync
      apiUrl?: string                           // Sonor API URL
      apiKey?: string                           // Sonor API key
      disableSync?: boolean                     // Skip Sonor sync (default: false)
      awaitMetaOptimization?: boolean           // Wait for Signal meta optimization
    
      // GEO / llms.txt integration
      optimizedLLMsTxt?: boolean                // Write AI-optimized llms.txt at build (default: true)
      optimizedLLMsFullTxt?: boolean            // Also write llms-full.txt
      includeLlmsTxtInSitemap?: boolean         // Add /llms.txt to sitemap (default: false; leave it off)
      includeLlmsFullTxtInSitemap?: boolean     // Add /llms-full.txt (default: false; leave it off)
    
      // Intelligent priority (requires Signal)
      intelligentPriority?: boolean             // Use visibility scores + depth heuristics
    
      // Local data for llms.txt fallback
      getLocalData?: () => Promise<LLMsDataResponse | null>
      llmsPublicSummaryOnly?: boolean
    }

    How Page Discovery Works

    1. Scans app/ directory recursively for page.tsx/page.jsx files
    2. Detects dynamic segments ([slug], [...catchAll]) and resolves them via:
      • dynamicRoutes config (highest priority)
      • Auto-import of generateStaticParams() from the page file (5s timeout)
    3. Fetches portfolio paths via getPortfolioPaths() if portfolio module is used
    4. Deduplicates and filters exclusion patterns
    5. Infers pageType for each page (homepage, service, article, faq, etc.)

    Trailing Slashes

    Every <loc> is the URL the site actually serves. With trailingSlash: true in next.config, Next 308-redirects /about to /about/, so createSitemap emits /about/. The exceptions follow Next's own redirects: / itself, file-like paths (a . in the last segment, like /llms.txt or /feed.xml) and /.well-known/*.

    You don't need to set anything. The option defaults to the site's next.config value, which Next inlines into the bundle. Set trailingSlash only if the kit is loaded outside Next's bundler (serverExternalPackages).

    Only the emitted URL changes. Dedupe, exclude, priorities and the Sonor sync all use the unslashed path, which is how seo_pages stores it. The build-time llms.txt links follow the same setting. npx sonor-setup doctor warns (sitemap.trailing-slash) when a built sitemap's URLs don't match next.config.

    Portfolio Paths

    import { getPortfolioPaths } from '@sonordev/site-kit/sitemap'
    
    createSitemap({
      additionalPaths: () => getPortfolioPaths({ basePath: '/work', priority: 0.7 }),
    })

    Sonor Sync

    During next build only (not ISR), the sitemap entries are POSTed to Sonor via POST /api/public/seo/register-sitemap with full-replace mode. This keeps seo_pages in sync with the actual site structure.

    Build-Time llms.txt

    When optimizedLLMsTxt is true (default), the sitemap build also writes public/llms.txt (and optionally public/llms-full.txt) using writeLLMsTxtToPublic(). This is served as a static file by the llms route handler.

    If the Sonor API is unreachable at build time and local data can't produce real content, the write is skipped (with a warning) rather than replaced with an "Information not available." stub — any existing public/llms.txt / public/llms-full.txt from a previous successful build keeps serving.

    It often won't fit in the route — write it from your postbuild

    Sonor generates llms.txt with an LLM call that can take a minute, and Next gives a prerendered route 60s before it retries and fails the build. So the in-route write gets only what's left of a 50s share of that budget: it refreshes the file when Sonor is quick, and otherwise leaves the existing file serving (the warning says so). It never fails the build.

    For a refresh on every build, move the write to your postbuild, where nothing imposes a ceiling and the sync it reads has already run:

    // package.json
    "scripts": { "postbuild": "sonor-register-sitemap --write-llms" }
    // app/sitemap.ts — one writer owns the file
    export default createSitemap({ baseUrl, optimizedLLMsTxt: false })

    Add --write-llms-full for public/llms-full.txt. The flag works even when the CLI skips its own sync because a sitemap route owns it.