Install sitemap via npm
masterTo use the sitemap library in your project, install it using npm:
npm install --save sitemaprepository·master·Indexed 23 days ago
https://github.com/ekalinin/sitemap.jsA high-level streaming sitemap-generating library and CLI for creating XML sitemaps. It supports generating single sitemaps, large sitemap indexes, and serving sitemaps dynamically via web servers. Key features include SitemapStream for XML generation, simpleSitemapAndIndex for handling large URL lists, and XMLToSitemapItemStream for parsing existing sitemaps.
To use the sitemap library in your project, install it using npm:
npm install --save sitemapFor websites with fewer than 50,000 URLs, you can serve a sitemap dynamically using an Express route. It is recommended to cache the generated sitemap in memory to avoid re-generating it on every request.
Note: When serving, ensure you set the Content-Type to application/xml and Content-Encoding to gzip if you are compressing the output.
// ESM
import express from 'express'
import { SitemapStream, streamToPromise } from 'sitemap'
import { createGzip } from 'zlib'
import { Readable } from 'stream'
const app = express()
let sitemap
app.get('/sitemap.xml', function(req, res) {
res.header('Content-Type', 'application/xml');
res.header('Content-Encoding', 'gzip');
// if we have a cached entry send it
if (sitemap) {
res.send(sitemap)
return
}
try {
const smStream = new SitemapStream({ hostname: 'https://example.com/' })
const pipeline = smStream.pipe(createGzip())
// pipe your entries or directly write them.
smStream.write({ url: '/page-1/', changefreq: 'daily', priority: 0.3 })
smStream.write({ url: '/page-2/', changefreq: 'monthly', priority: 0.7 })
smStream.write({ url: '/page-3/'}) // changefreq: 'weekly', priority: 0.5
smStream.write({ url: '/page-4/', img: "http://urlTest.com" })
/* or use
Readable.from([{url: '/page-1'}...]).pipe(smStream)
if you are looking to avoid writing your own loop.
*/
// cache the response
streamToPromise(pipeline).then(sm => sitemap = sm)
// make sure to attach a write stream such as streamToPromise before ending
smStream.end()
// stream write the response
pipeline.pipe(res).on('error', (e) => {throw e})
} catch (e) {
console.error(e)
res.status(500).end()
}
})
app.listen(3000, () => {
console.log('listening')
});You can selectively process or delete URLs from an existing sitemap by piping an XMLToSitemapItemStream through a custom Transform stream. This is useful for extracting specific subsets of URLs (e.g., only URLs containing /blog/) from a large sitemap file.
import { createReadStream } from 'fs'
import { Transform } from 'stream'
import { XMLToSitemapItemStream } from 'sitemap'
// Create a filter that only keeps certain URLs
const filterStream = new Transform({
objectMode: true,
transform(item, encoding, callback) {
// Only keep URLs containing '/blog/'
if (item.url.includes('/blog/')) {
callback(undefined, item) // Keep this item
} else {
callback() // Skip this item (effectively "deleting" it)
}
}
})
// Parse and filter
createReadStream('./sitemap.xml')
.pipe(new XMLToSitemapItemStream())
.pipe(filterStream)
.on('data', (item) => {
console.log('Filtered URL:', item.url)
})If you have more than 50,000 URLs, you should use simpleSitemapAndIndex or SitemapAndIndexStream to split your URLs into multiple sitemap files and generate a sitemap index.
simpleSitemapAndIndexThis is a high-level function that handles the creation of multiple sitemaps and an index file automatically.
SitemapAndIndexStreamFor more control, use SitemapAndIndexStream. You must provide a getSitemapStream function that returns an array containing: [url_of_the_sitemap, sitemap_stream, write_stream]. This function is called every time a new sitemap file needs to be created.
// ESM
import { createReadStream, createWriteStream } from 'fs'
import { resolve } from 'path'
import { createGzip } from 'zlib'
import { simpleSitemapAndIndex, lineSeparatedURLsToSitemapOptions } from 'sitemap'
// writes sitemaps and index out to the destination you provide.
simpleSitemapAndIndex({
hostname: 'https://example.com',
destinationDir: './',
sourceData: lineSeparatedURLsToSitemapOptions(
createReadStream('./your-data.json.txt')
),
// sourceData can also be:
// sourceData: [{ url: '/page-1/', changefreq: 'daily'}, ...],
// or
// sourceData: './your-data.json.txt',
limit: 45000, // optional, default: 50000
gzip: true, // optional, default: true
publicBasePath: '/sitemaps/', // optional, default: './'
xslUrl: 'https://example.com/sitemap.xsl', // optional XSL stylesheet
}).then(() => {
// Do follow up actions
})The getSitemapStream callback is mandatory for SitemapAndIndexStream. It is called whenever the current sitemap reaches the configured limit.
It must return a 3-element tuple:
IndexItem | string: The entry that will be added to the sitemap index (usually the URL of the sitemap file being created).SitemapStream: The stream instance used to write the actual sitemap items.WriteStream: The destination stream (e.g., a file write stream) that the SitemapStream should be piped to. The SitemapAndIndexStream will wait for this write stream to finish before proceeding to the next sitemap.// Example implementation
const getSitemapStream = (i: number) => {
const sitemapStream = new SitemapStream();
const path = `./sitemap-${i}.xml`;
const writeStream = createWriteStream(path);
// Pipe the sitemap stream to the file
sitemapStream.pipe(writeStream);
// Return the tuple: [URL for index, the sitemap stream, the file destination]
return [`https://example.com/${path}`, sitemapStream, writeStream];
};When building sitemap entries, you can use SitemapItem for strict, normalized entries or SitemapItemLoose for more flexible input before normalization.
SitemapItem (Strict)Requires explicit arrays for img, video, and links.
SitemapItemLoose (Flexible)Allows for single values or arrays for img and video, and provides alternative ways to specify dates:
lastmodfile: string | Buffer | URLlastmodISO: stringlastmodrealtime: booleanexport interface SitemapItem extends SitemapItemBase {
img: Img[];
video: VideoItem[];
links: LinkItem[];
}
export interface SitemapItemLoose extends SitemapItemBase {
video?: VideoItemLoose | VideoItemLoose[];
img?: string | Img | (string | Img)[];
links?: LinkItem[];
lastmodfile?: string | Buffer | URL;
lastmodISO?: string;
lastmodrealtime?: boolean;
}The xmlns option in SitemapStream accepts an NSArgs object to enable specific XML namespaces in the <urlset> tag. By default, news, xhtml, image, and video are enabled.
Available Keys:
news: Google News sitemap namespace.video: Google Video sitemap namespace.xhtml: XHTML namespace.image: Image sitemap namespace.custom: An array of strings for custom namespace declarations (e.g., xmlns:prefix="uri" or prefix:attribute="value").Security Note: Custom namespaces are strictly validated to prevent XML injection and DoS attacks. They must follow the format prefix:name="value".
The sitemap CLI tool converts a list of URLs (provided via stdin or a file) into a sitemap XML. It supports generating single sitemaps, sitemap indexes, and compressing output with Gzip.
To generate a single sitemap from a file and output it to stdout:
npx sitemap < listofurls.txt > sitemap.xmlTo create a sitemap index file and multiple individual sitemaps, use the --index flag. You must provide a --index-base-url so the index can point to the correct locations of the generated sitemaps.
npx sitemap --gzip --index --index-base-url https://example.com/path/to/sitemaps/ < listofurls.txt > sitemap-index.xml.gzTo add new URLs to an existing sitemap file, use the --prepend flag:
npx sitemap --prepend sitemap.xml < listofurls.jsonnpx sitemap --gzip --index --index-base-url https://example.com/path/to/sitemaps/ < listofurls.txt > sitemap-index.xml.gzYou can generate a sitemap from an array of link objects using SitemapStream and streamToPromise. This is useful for one-off generation tasks.
Each link object can include properties like url, changefreq, and priority.
// ESM
import { SitemapStream, streamToPromise } from 'sitemap'
import { Readable } from 'stream'
// An array with your links
const links = [{ url: '/page-1/', changefreq: 'daily', priority: 0.3 }]
// Create a stream to write to
const stream = new SitemapStream( { hostname: 'https://...' } )
// Return a promise that resolves with your XML string
return streamToPromise(Readable.from(links).pipe(stream)).then((data) =>
data.toString()
)The xmlLint function validates whether a provided XML string or Readable stream is a valid sitemap. This is a wrapper around the xmllint command-line tool and requires it to be installed on your system.
Security Note: To prevent command injection, xmlLint accepts only XML content as a string or stream; it does not accept file paths.
import { createReadStream, readFileSync } from 'fs';
import { xmlLint } from 'sitemap';
// Validate using a stream
xmlLint(createReadStream('./example.xml')).then(
() => console.log('xml is valid'),
([err, stderr]) => console.error('xml is invalid', stderr)
)
// Validate using a string
const xmlContent = readFileSync('./example.xml', 'utf8');
xmlLint(xmlContent).then(
() => console.log('xml is valid'),
([err, stderr]) => console.error('xml is invalid', stderr)
)streamToPromise utility takes a stream and returns a Promise that resolves when the stream emits the finish event. This is useful for capturing the full buffer emitted by a SitemapStream.When instantiating SitemapStream, you can pass several configuration options to control the XML output, including namespaces and XSL stylesheets.
Key options include:
hostname: The base URL for all links.xslUrl: An optional XSL stylesheet URL.lastmodDateOnly: If true, only the date is printed, not the time.xmlns: An object to manage XML namespaces (e.g., news, xhtml, image, video, custom).// ESM
import { SitemapStream, streamToPromise } from 'sitemap'
import { Readable } from 'stream'
const smStream = new SitemapStream({
hostname: 'http://www.mywebsite.com',
xslUrl: "https://example.com/style.xsl",
lastmodDateOnly: false,
xmlns: {
news: true,
xhtml: true,
image: true,
video: true,
custom: [
'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"',
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
],
}
})
// write an entry with various metadata
smStream.write({
url: 'http://test.com/page-1/',
img: [
{
url: 'http://test.com/img1.jpg',
caption: 'An image',
title: 'The Title of Image One',
geoLocation: 'London, United Kingdom',
license: 'https://creativecommons.org/licenses/by/4.0/'
}
],
video: [
{
thumbnail_loc: 'http://test.com/tmbn1.jpg',
title: 'A video title',
description: 'This is a video'
}
],
links: [
{ lang: 'en', url: 'http://test.com/page-1/' },
{ lang: 'ja', url: 'http://test.com/page-1/ja/' }
],
androidLink: 'android-app://com.company.test/page-1/',
news: {
publication: {
name: 'The Example Times',
language: 'en'
},
genres: 'PressRelease, Blog',
publication_date: '2008-12-23',
title: 'Companies, A, B in Merger Talks',
keywords: 'business, merger, acquisition, A, B',
stock_tickers: 'NASDAQ:A, NASDAQ:B'
}
})
smStream.end()