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
- Scans
app/directory recursively forpage.tsx/page.jsxfiles - Detects dynamic segments (
[slug],[...catchAll]) and resolves them via:dynamicRoutesconfig (highest priority)- Auto-import of
generateStaticParams()from the page file (5s timeout)
- Fetches portfolio paths via
getPortfolioPaths()if portfolio module is used - Deduplicates and filters exclusion patterns
- Infers
pageTypefor 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.