# 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

```ts
// app/sitemap.ts
import { createSitemap } from '@sonordev/site-kit/sitemap'

export default createSitemap({
  baseUrl: 'https://example.com',
})
```

## Config

```ts
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

```ts
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:

```jsonc
// package.json
"scripts": { "postbuild": "sonor-register-sitemap --write-llms" }
```

```ts
// 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.
