@power-seo/sitemap
v1.0.12
Published
XML sitemap generation, streaming, and validation with image, video, and news support
Downloads
895
Maintainers
Readme
@power-seo/sitemap
XML sitemap generation for TypeScript — streaming output, automatic index splitting, image/video/news extensions, and URL validation — works in Next.js, Remix, Express, and edge runtimes with zero runtime dependencies.
@power-seo/sitemap produces standards-compliant <urlset> and <sitemapindex> XML from typed URL arrays. Provide a hostname and URL list — get back a valid XML string ready to serve as Content-Type: application/xml. For large catalogs, stream chunks with constant memory usage or auto-split at the 50,000-URL spec limit with a generated index file. All six functions are independently importable and tree-shakeable.
Zero runtime dependencies — only
@power-seo/coreas a peer.
Why @power-seo/sitemap?
| | Without | With |
| ----------------- | ----------------------------------------- | --------------------------------------------------- |
| Spec compliance | ❌ Hand-built XML, wrong namespaces | ✅ Correct <urlset> + namespace declarations |
| Large sites | ❌ Single file breaks at 50,000 URLs | ✅ Auto-split + sitemap index generation |
| Memory usage | ❌ String concat spikes on large catalogs | ✅ Synchronous generator yields chunks |
| Image indexing | ❌ Product images undiscoverable | ✅ <image:image> extension per URL |
| Video SEO | ❌ No structured video metadata | ✅ <video:video> extension with title, duration |
| News sitemaps | ❌ Missing publication + date tags | ✅ <news:news> extension for Google News |
| Hostname handling | ❌ Hardcode absolute URLs everywhere | ✅ Pass hostname once; use relative loc paths |
| Validation | ❌ Silent bad data reaches Google | ✅ validateSitemapUrl() returns errors + warnings |
Features
- Full sitemap spec support —
<loc>,<lastmod>,<changefreq>,<priority>, all optional elements - Hostname + relative paths — pass
hostnamein config;loccan be a relative path like/about - Image sitemap extension —
<image:image>tags withloc,caption,title,geoLocation,license - Video sitemap extension —
<video:video>tags with title, description, thumbnail, duration, rating - News sitemap extension —
<news:news>tags with publication name, language, date - Streaming generation —
streamSitemap()is a synchronous generator yielding XML string chunks; no memory spike on large lists - Automatic index splitting —
splitSitemap()chunks atMAX_URLS_PER_SITEMAP(50,000) and returns both sitemaps and the index XML - Sitemap index generation —
generateSitemapIndex()creates a<sitemapindex>pointing to child sitemaps - Smart namespace detection —
generateSitemap()only declares XML namespaces for extensions (image, video, news) that are actually used - URL validation —
validateSitemapUrl()returns{ valid, errors, warnings }without throwing - Next.js App Router adapter —
toNextSitemap()convertsSitemapURL[]to theMetadataRoute.Sitemap[]format forapp/sitemap.ts - Constants exported —
MAX_URLS_PER_SITEMAP(50,000) andMAX_SITEMAP_SIZE_BYTES(52,428,800) - Framework-agnostic — works in Next.js API routes, Remix loaders, Express, Fastify, and edge runtimes
- Full TypeScript support — typed
SitemapURL,SitemapImage,SitemapVideo,SitemapNews,SitemapConfig - Zero runtime dependencies — pure TypeScript, no external XML libraries
- Tree-shakeable — import only the functions you use
Comparison
| Feature | @power-seo/sitemap | next-sitemap | sitemap (npm) | xmlbuilder2 |
| -------------------------------- | :----------------: | :----------: | :-----------: | :---------: |
| Image sitemap extension | ✅ | ✅ | ✅ | ❌ |
| Video sitemap extension | ✅ | ❌ | ✅ | ❌ |
| News sitemap extension | ✅ | ❌ | ✅ | ❌ |
| Streaming generation | ✅ | ❌ | ❌ | ❌ |
| Auto index splitting | ✅ | ✅ | ❌ | ❌ |
| URL validation | ✅ | ❌ | ❌ | ❌ |
| Hostname + relative loc paths | ✅ | ❌ | ❌ | ❌ |
| Zero runtime dependencies | ✅ | ❌ | ❌ | ❌ |
| Edge runtime compatible | ✅ | ❌ | ❌ | ❌ |
| TypeScript-first | ✅ | Partial | ❌ | ❌ |
| Tree-shakeable | ✅ | ❌ | ❌ | ❌ |
| Next.js app/sitemap.ts adapter | ✅ | ✅ | ❌ | ❌ |
Installation
npm install @power-seo/sitemapyarn add @power-seo/sitemappnpm add @power-seo/sitemapQuick Start
import { generateSitemap } from '@power-seo/sitemap';
const xml = generateSitemap({
hostname: 'https://example.com',
urls: [
{ loc: '/', lastmod: '2026-01-01', changefreq: 'daily', priority: 1.0 },
{ loc: '/about', changefreq: 'monthly', priority: 0.8 },
{ loc: '/blog/post-1', lastmod: '2026-01-15', priority: 0.6 },
],
});
// Returns valid XML string:
// <?xml version="1.0" encoding="UTF-8"?>
// <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
// <url><loc>https://example.com/</loc>...hostname is required — it is prepended to any loc value that is a relative path. Absolute loc values (starting with http) are used as-is.
Usage
Generating a Sitemap
generateSitemap() accepts a SitemapConfig with hostname and urls and returns a complete XML string.
import { generateSitemap } from '@power-seo/sitemap';
const xml = generateSitemap({
hostname: 'https://example.com',
urls: [
{ loc: '/', lastmod: '2026-01-01', changefreq: 'daily', priority: 1.0 },
{ loc: '/products', changefreq: 'weekly', priority: 0.9 },
{ loc: '/blog', changefreq: 'daily', priority: 0.8 },
],
});
// Serve as application/xml
res.setHeader('Content-Type', 'application/xml');
res.send(xml);Streaming a Large Sitemap
streamSitemap() is a synchronous generator. It yields XML string chunks one <url> at a time — keeping memory usage constant regardless of catalog size.
import { streamSitemap } from '@power-seo/sitemap';
const urls = fetchAllProductUrls(); // Iterable<SitemapURL>
const stream = streamSitemap('https://example.com', urls);
for (const chunk of stream) {
response.write(chunk);
}
response.end();Splitting Large Sitemaps with an Index
splitSitemap() chunks a config at the 50,000-URL spec limit and returns all individual sitemap XML strings plus a sitemap index XML string that references them.
import { splitSitemap } from '@power-seo/sitemap';
const { index, sitemaps } = splitSitemap({
hostname: 'https://example.com',
urls: largeUrlArray, // more than 50,000 entries
});
// Write each sitemap file
for (const { filename, xml } of sitemaps) {
fs.writeFileSync(`./public${filename}`, xml);
}
// Write the index (default filenames: /sitemap-0.xml, /sitemap-1.xml, ...)
fs.writeFileSync('./public/sitemap.xml', index);Custom filename pattern:
const { index, sitemaps } = splitSitemap(
{ hostname: 'https://example.com', urls: largeUrlArray },
'/sitemaps/part-{index}.xml', // default: '/sitemap-{index}.xml'
);Generating a Sitemap Index Manually
Use generateSitemapIndex() when you maintain separate sitemaps per section or locale and want to combine them under a single index file.
import { generateSitemapIndex } from '@power-seo/sitemap';
const indexXml = generateSitemapIndex({
sitemaps: [
{ loc: 'https://example.com/sitemap-pages.xml', lastmod: '2026-01-01' },
{ loc: 'https://example.com/sitemap-products.xml', lastmod: '2026-01-15' },
{ loc: 'https://example.com/sitemap-blog.xml', lastmod: '2026-01-20' },
],
});Image Sitemaps
Add images to any SitemapURL entry to emit <image:image> extension tags:
import { generateSitemap } from '@power-seo/sitemap';
const xml = generateSitemap({
hostname: 'https://example.com',
urls: [
{
loc: '/products/blue-sneaker',
lastmod: '2026-01-10',
images: [
{
loc: 'https://cdn.example.com/sneaker-blue.jpg',
caption: 'Blue sneaker — side view',
title: 'Blue Running Sneaker',
},
{
loc: 'https://cdn.example.com/sneaker-blue-top.jpg',
caption: 'Blue sneaker — top view',
},
],
},
],
});Validating URL Entries
validateSitemapUrl() checks a SitemapURL against the sitemap spec and returns structured errors and warnings — useful in CI or before serving.
import { validateSitemapUrl } from '@power-seo/sitemap';
const result = validateSitemapUrl({
loc: '/about',
priority: 1.5, // out of range
changefreq: 'daily',
});
// result.valid → false
// result.errors → ['priority must be between 0.0 and 1.0']
// result.warnings → []Next.js App Router — app/sitemap.ts Convention
Next.js App Router has a built-in app/sitemap.ts file convention that returns an array of URL objects (not XML). Use toNextSitemap() to convert SitemapURL[] to the required format:
// app/sitemap.ts
import { toNextSitemap } from '@power-seo/sitemap';
export default async function sitemap() {
const urls = await fetchUrlsFromCms();
return toNextSitemap(urls);
// Returns NextSitemapEntry[] — Next.js renders the XML automatically
}toNextSitemap() filters out invalid URLs and maps changefreq to changeFrequency as required by Next.js. The lastmod field is passed through as-is (string or Date).
Next.js App Router — Route Handler (XML)
For full control over the XML output (useful when you need image/video/news extensions), use a route handler instead:
// app/sitemap.xml/route.ts
import { generateSitemap } from '@power-seo/sitemap';
export async function GET() {
const urls = await fetchUrlsFromCms();
const xml = generateSitemap({
hostname: 'https://example.com',
urls,
});
return new Response(xml, {
headers: { 'Content-Type': 'application/xml' },
});
}Remix Resource Route
// app/routes/sitemap[.xml].ts
import { generateSitemap } from '@power-seo/sitemap';
import type { LoaderFunctionArgs } from '@remix-run/node';
export async function loader({ request }: LoaderFunctionArgs) {
const urls = await fetchUrlsFromDb();
const xml = generateSitemap({
hostname: 'https://example.com',
urls,
});
return new Response(xml, {
headers: { 'Content-Type': 'application/xml' },
});
}API Reference
generateSitemap(config)
function generateSitemap(config: SitemapConfig): string;| Prop | Type | Required | Description |
| ------------------- | -------------- | -------- | ------------------------------------------------------------------------- |
| hostname | string | ✅ | Base URL prepended to relative loc paths (e.g. 'https://example.com') |
| urls | SitemapURL[] | ✅ | Array of URL entries |
| maxUrlsPerSitemap | number | — | Override the 50,000-URL chunk size (used by splitSitemap) |
| outputDir | string | — | Optional output directory hint (informational; does not write files) |
streamSitemap(hostname, urls)
function streamSitemap(
hostname: string,
urls: Iterable<SitemapURL>,
): Generator<string, void, undefined>;Synchronous generator. Yields XML string chunks — one for the XML declaration and opening tag, one per <url> block, and one for the closing tag. Does not buffer the full XML in memory.
| Param | Type | Description |
| ---------- | ---------------------- | ------------------------------------------------------------------ |
| hostname | string | Base URL prepended to relative loc paths |
| urls | Iterable<SitemapURL> | Any iterable of URL entries — arrays, generators, database cursors |
splitSitemap(config, sitemapUrlPattern?)
function splitSitemap(
config: SitemapConfig,
sitemapUrlPattern?: string,
): { index: string; sitemaps: Array<{ filename: string; xml: string }> };Splits a large URL set into multiple sitemap files and returns the index XML and all sitemap XMLs. The sitemapUrlPattern parameter controls generated filenames using {index} as a placeholder.
| Param | Type | Default | Description |
| ------------------- | --------------- | ------------------------ | --------------------------------------- |
| config | SitemapConfig | — | Same config as generateSitemap() |
| sitemapUrlPattern | string | '/sitemap-{index}.xml' | Filename pattern for each split sitemap |
Return value:
| Field | Type | Description |
| ---------- | ------------------------------------------ | ---------------------------------------------------------------- |
| index | string | Sitemap index XML (<sitemapindex>) referencing all split files |
| sitemaps | Array<{ filename: string; xml: string }> | Each split sitemap with its filename and XML string |
generateSitemapIndex(config)
function generateSitemapIndex(config: SitemapIndexConfig): string;| Prop | Type | Description |
| ---------- | --------------------- | ---------------------------------------------------- |
| sitemaps | SitemapIndexEntry[] | Array of { loc: string; lastmod?: string } entries |
validateSitemapUrl(url)
function validateSitemapUrl(url: SitemapURL): SitemapValidationResult;Returns { valid: boolean; errors: string[]; warnings: string[] }. Never throws.
toNextSitemap(urls)
import { toNextSitemap } from '@power-seo/sitemap';
function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[];Converts a SitemapURL[] to the array format expected by Next.js App Router's app/sitemap.ts file convention. Invalid URLs (per validateSitemapUrl) are filtered out automatically. changefreq is mapped to changeFrequency.
| Field | Type | Description |
| ----------------- | ---------------- | ------------------------------------- |
| url | string | Absolute URL (loc) |
| lastModified | Date \| string | From lastmod (passed through as-is) |
| changeFrequency | string | From changefreq |
| priority | number | From priority |
Types
| Type | Description |
| ------------------------- | --------------------------------------------------------------------------------------------- |
| SitemapConfig | { hostname: string; urls: SitemapURL[]; maxUrlsPerSitemap?: number; outputDir?: string } |
| SitemapURL | Single URL entry — see field table below |
| SitemapImage | { loc: string; caption?: string; geoLocation?: string; title?: string; license?: string } |
| SitemapVideo | Video extension entry with thumbnailLoc, title, description, and optional fields |
| SitemapNews | { publication: { name: string; language: string }; publicationDate: string; title: string } |
| SitemapIndexConfig | { sitemaps: SitemapIndexEntry[] } |
| SitemapIndexEntry | { loc: string; lastmod?: string } |
| SitemapValidationResult | { valid: boolean; errors: string[]; warnings: string[] } |
SitemapURL Fields
| Prop | Type | Default | Description |
| ------------ | --------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------ |
| loc | string | — | Required. URL path (e.g. /about) or absolute URL. Hostname is prepended to relative paths. |
| lastmod | string | — | Last modified date — ISO 8601 or YYYY-MM-DD |
| changefreq | 'always' \| 'hourly' \| 'daily' \| 'weekly' \| 'monthly' \| 'yearly' \| 'never' | — | Suggested crawl frequency |
| priority | number | (no tag emitted) | Priority 0.0–1.0. When omitted, no <priority> tag is written. |
| images | SitemapImage[] | — | Image extension entries — emits <image:image> blocks |
| videos | SitemapVideo[] | — | Video extension entries — emits <video:video> blocks |
| news | SitemapNews | — | News extension entry — emits <news:news> block |
Constants
| Constant | Value | Description |
| ------------------------ | ------------ | ------------------------------------------------------------- |
| MAX_URLS_PER_SITEMAP | 50_000 | Maximum URLs allowed per sitemap file (spec limit) |
| MAX_SITEMAP_SIZE_BYTES | 52_428_800 | Maximum sitemap file size in bytes (50 MB = 50 × 1024 × 1024) |
Use Cases
- Next.js App Router — generate sitemaps in
app/sitemap.xml/route.tsat request time or build time - E-commerce catalogs — product image sitemaps with
<image:image>for every listing; keep Google Images up to date - News publishers —
<news:news>extension for Google News sitemap submission - Multi-locale sites — separate sitemaps per locale with a unified sitemap index
- Programmatic SEO — generate sitemaps for thousands of auto-generated pages with no memory overhead
- Large sites — automatic splitting at 50,000 URLs per file with a generated index
- Video platforms —
<video:video>extension with title, description, and thumbnail for video SEO - CI/CD pipelines — validate URL entries with
validateSitemapUrl()as part of pull request checks
Architecture Overview
- Pure TypeScript — no compiled binary, no native modules
- Zero runtime dependencies — only
@power-seo/coreas a peer dependency - Framework-agnostic — works in any JavaScript environment that supports ES2020+
- SSR compatible — safe to run in Next.js Server Components, Remix loaders, or Express handlers
- Edge runtime safe — no
fs, nopath, no Node.js-specific APIs; runs in Cloudflare Workers, Vercel Edge, Deno - Synchronous generator streaming —
streamSitemap()usesfunction*— no async overhead, no backpressure complexity - Smart namespace detection —
generateSitemap()only declares image/video/news namespaces when actually used;streamSitemap()always includes all namespaces for simplicity - Tree-shakeable —
"sideEffects": falsewith named exports per function - Dual ESM + CJS — ships both formats via tsup for any bundler or
require()usage
Supply Chain Security
- No install scripts (
postinstall,preinstall) - No runtime network access
- No
evalor dynamic code execution - CI-signed builds — all releases published via verified
github.com/CyberCraftBD/power-seoworkflow - Safe for SSR, Edge, and server environments
The @power-seo Ecosystem
All 17 packages are independently installable — use only what you need.
| Package | Install | Description |
| ------------------------------------------------------------------------------------------ | ----------------------------------- | ----------------------------------------------------------------------- |
| @power-seo/core | npm i @power-seo/core | Framework-agnostic utilities, types, validators, and constants |
| @power-seo/react | npm i @power-seo/react | React SEO components — meta, Open Graph, Twitter Card, breadcrumbs |
| @power-seo/meta | npm i @power-seo/meta | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR |
| @power-seo/schema | npm i @power-seo/schema | Type-safe JSON-LD structured data — 23 builders + 22 React components |
| @power-seo/content-analysis | npm i @power-seo/content-analysis | Yoast-style SEO content scoring engine with React components |
| @power-seo/readability | npm i @power-seo/readability | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI |
| @power-seo/preview | npm i @power-seo/preview | SERP, Open Graph, and Twitter/X Card preview generators |
| @power-seo/sitemap | npm i @power-seo/sitemap | XML sitemap generation, streaming, index splitting, and validation |
| @power-seo/redirects | npm i @power-seo/redirects | Redirect engine with Next.js, Remix, and Express adapters |
| @power-seo/links | npm i @power-seo/links | Link graph analysis — orphan detection, suggestions, equity scoring |
| @power-seo/audit | npm i @power-seo/audit | Full SEO audit engine — meta, content, structure, performance rules |
| @power-seo/images | npm i @power-seo/images | Image SEO — alt text, lazy loading, format analysis, image sitemaps |
| @power-seo/ai | npm i @power-seo/ai | LLM-agnostic AI prompt templates and parsers for SEO tasks |
| @power-seo/analytics | npm i @power-seo/analytics | Merge GSC + audit data, trend analysis, ranking insights, dashboard |
| @power-seo/search-console | npm i @power-seo/search-console | Google Search Console API — OAuth2, service account, URL inspection |
| @power-seo/integrations | npm i @power-seo/integrations | Semrush and Ahrefs API clients with rate limiting and pagination |
| @power-seo/tracking | npm i @power-seo/tracking | GA4, Clarity, PostHog, Plausible, Fathom — scripts + consent management |
About CyberCraft Bangladesh
CyberCraft Bangladesh is a Bangladesh-based enterprise-grade software development and Full Stack SEO service provider company specializing in ERP system development, AI-powered SaaS and business applications, full-stack SEO services, custom website development, and scalable eCommerce platforms. We design and develop intelligent, automation-driven SaaS and enterprise solutions that help startups, SMEs, NGOs, educational institutes, and large organizations streamline operations, enhance digital visibility, and accelerate growth through modern cloud-native technologies.
© 2026 CyberCraft Bangladesh · Released under the MIT License
