/** * @lacspace/sitemap — validation & formatting helpers. * * Small, zero-dependency utilities for keeping sitemaps spec-compliant: * clamp `priority`, validate `changefreq`, format `lastmod` as W3C/ISO, and * guard against the 50,000-URL-per-file limit with a clear error. * * All additive — the core builders (`sitemap`, `splitSitemaps`, …) already * clamp priority and format dates internally; these expose the same rules so * callers can validate their data before building. */ /** Max URLs a single sitemap file may contain (sitemaps.org spec). */ declare const SITEMAP_MAX_URLS = 50000; /** The seven valid `` values, in spec order. */ declare const CHANGEFREQS: readonly ChangeFreq[]; /** Type guard: is `value` one of the seven valid `` values? */ declare function isValidChangefreq(value: unknown): value is ChangeFreq; /** * Clamp a `priority` into the valid 0.0–1.0 range and round to one decimal * (matching the value the builders actually emit). Non-finite input (`NaN`, * `Infinity`) falls back to the sitemap default of `0.5`. * @example clampPriority(1.7) // 1 * @example clampPriority(-3) // 0 * @example clampPriority(0.83) // 0.8 */ declare function clampPriority(priority: number): number; /** * Format a `lastmod` value as a W3C Datetime string (ISO 8601). * A `Date` becomes its full `toISOString()`; a string is passed through * unchanged (assumed already valid). Invalid `Date`s throw a clear error. * @example formatLastmod(new Date("2026-09-05T00:00:00Z")) // "2026-09-05T00:00:00.000Z" */ declare function formatLastmod(date: Date | string): string; /** * Throw a clear `RangeError` if `count` exceeds the per-file limit. Use before * writing a single sitemap file; for larger sets use {@link splitSitemaps}. * @param count number of URLs * @param cap max allowed (default {@link SITEMAP_MAX_URLS}); values above the * spec max are themselves capped at 50,000. */ declare function assertUrlCount(count: number, cap?: number): void; /** A single validation problem found on a URL entry. */ interface SitemapUrlIssue { /** The offending field. */ field: "loc" | "priority" | "changefreq" | "lastmod"; /** Human-readable description. */ message: string; } /** * Non-throwing validation of one URL-like entry. Returns a list of issues * (empty = valid). Checks that `loc` is a non-empty absolute URL, `priority` * is within 0.0–1.0, `changefreq` is a valid enum value, and `lastmod` parses. * @example validateSitemapUrl({ loc: "/rel", priority: 2 }) // 2 issues */ declare function validateSitemapUrl(u: { loc?: unknown; priority?: unknown; changefreq?: unknown; lastmod?: unknown; }): SitemapUrlIssue[]; /** * @lacspace/sitemap * Generate sitemap.xml, sitemap indexes and Next.js sitemaps. * * URL entries with lastmod / changefreq / priority, image / video / news * extensions and hreflang alternates. Auto-splits large sets into an index. * * Zero dependencies · isomorphic · fully typed. */ type ChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"; interface SitemapImage { loc: string; title?: string; caption?: string; } interface SitemapVideo { thumbnailLoc: string; title: string; description: string; contentLoc?: string; playerLoc?: string; duration?: number; } interface SitemapNews { publicationName: string; language: string; title: string; publicationDate: string | Date; } interface Alternate { hreflang: string; href: string; } interface SitemapUrl { loc: string; lastmod?: string | Date; changefreq?: ChangeFreq; /** 0.0–1.0 */ priority?: number; images?: SitemapImage[]; videos?: SitemapVideo[]; news?: SitemapNews; alternates?: Alternate[]; } interface SitemapOptions { /** * URL of an XSL stylesheet. When set, a `` processing * instruction is added so the raw sitemap renders human-readably in a browser. * @see {@link sitemapStylesheet} */ stylesheet?: string; /** * Cap to enforce instead of the sitemaps.org max of 50,000 (values above it * are themselves capped at 50,000). Use {@link splitSitemaps} for larger sets. */ maxUrls?: number; /** * Skip the URL-count check entirely and emit an oversized file. * * The 50,000 cap is enforced by default because Google rejects the whole file * past it — emitting 50,001 URLs produced an artifact no crawler would read. * Only set this when the sitemap is for a consumer that has no such limit. * @default false */ allowOversize?: boolean; } /** Build a single sitemap.xml document. */ declare function sitemap(urls: SitemapUrl[], opts?: SitemapOptions): string; /** Build a sitemap index that points at multiple sitemaps. */ declare function sitemapIndex(sitemaps: { loc: string; lastmod?: string | Date; }[]): string; interface SplitOptions { /** Base URL where sitemap files will be hosted, e.g. "https://x.com". */ baseUrl: string; /** Max URLs per file (spec max 50,000). Default 50000. */ perFile?: number; /** Name for the i-th file (0-based). Default `sitemap-${i}.xml`. */ filename?: (i: number) => string; } /** Split a large URL set into an index + multiple sitemap files. */ declare function splitSitemaps(urls: SitemapUrl[], opts: SplitOptions): { index: string; files: { name: string; xml: string; }[]; }; interface NextSitemapEntry { url: string; lastModified?: string | Date; changeFrequency?: ChangeFreq; priority?: number; alternates?: { languages: Record; }; } /** Convert entries to the shape Next.js `sitemap.ts` expects (`MetadataRoute.Sitemap`). */ declare function toNextSitemap(urls: SitemapUrl[]): NextSitemapEntry[]; /** Minimal site shape shared across the Lacspace SEO Kit (structural, no imports). */ interface SiteLike { /** Absolute base URL, e.g. "https://acme.com". */ url: string; } /** A route: just a path string, or a full entry (its `loc`/`path` is resolved against the site URL). */ type SiteRoute = string | (Omit & { loc?: string; path?: string; }); /** * Build a sitemap from bare paths — no need to repeat your domain on every row. * Relative locs are resolved against the site URL; absolute ones pass through. * @example sitemapForSite({ url: "https://acme.com" }, ["/", "/pricing", { path: "/blog", changefreq: "daily", priority: 0.8 }]) */ declare function sitemapForSite(site: SiteLike, routes: SiteRoute[]): string; /** One entry of a Google News sitemap: a page URL plus its news metadata. */ interface NewsSitemapItem extends SitemapNews { loc: string; } /** * Build a Google News sitemap (`` per URL). * News sitemaps should only contain articles from the last 2 days. * @example newsSitemap([{ loc: "https://x.com/a", publicationName: "The Times", language: "en", title: "Headline", publicationDate: new Date() }]) */ declare function newsSitemap(items: NewsSitemapItem[], opts?: SitemapOptions): string; /** One entry of a video sitemap: a page URL plus a single video's metadata. */ interface VideoSitemapItem extends SitemapVideo { loc: string; } /** * Build a video extension sitemap (`` per URL). * @example videoSitemap([{ loc: "https://x.com/watch", thumbnailLoc: "https://x.com/t.jpg", title: "Clip", description: "…", contentLoc: "https://x.com/v.mp4", duration: 120 }]) */ declare function videoSitemap(items: VideoSitemapItem[], opts?: SitemapOptions): string; /** One entry of an image sitemap: a page URL plus the images it contains. */ interface ImageSitemapItem { loc: string; images: SitemapImage[]; } /** * Build an image extension sitemap (`` entries grouped per URL). * @example imageSitemap([{ loc: "https://x.com/gallery", images: [{ loc: "https://x.com/1.jpg", title: "One" }] }]) */ declare function imageSitemap(items: ImageSitemapItem[], opts?: SitemapOptions): string; /** * An XSL stylesheet that renders a raw `sitemap.xml` as a readable HTML table * in the browser. Serve it alongside your sitemap and reference it via the * `stylesheet` option on {@link sitemap} (or the extension sitemaps). * @example * // app/sitemap.xsl route → return sitemapStylesheet() * sitemap(urls, { stylesheet: "/sitemap.xsl" }) */ declare function sitemapStylesheet(): string; export { type Alternate, CHANGEFREQS, type ChangeFreq, type ImageSitemapItem, type NewsSitemapItem, type NextSitemapEntry, SITEMAP_MAX_URLS, type SiteLike, type SiteRoute, type SitemapImage, type SitemapNews, type SitemapOptions, type SitemapUrl, type SitemapUrlIssue, type SitemapVideo, type SplitOptions, type VideoSitemapItem, assertUrlCount, clampPriority, formatLastmod, imageSitemap, isValidChangefreq, newsSitemap, sitemap, sitemapForSite, sitemapIndex, sitemapStylesheet, splitSitemaps, toNextSitemap, validateSitemapUrl, videoSitemap };