Next.js Integration
Push URLs to SitemapHost from your Next.js app during build, on-demand, or via ISR
This guide covers how to integrate SitemapHost with a Next.js application. You will learn how to collect URLs from your pages and push them to SitemapHost automatically during builds, via API routes, or through Incremental Static Regeneration (ISR).
Prerequisites: A SitemapHost account, a configured domain, and an API key. See the Quick Start guide if you haven't set these up yet.
Why Use SitemapHost with Next.js?
Next.js generates pages at build time (SSG), on request (SSR), or incrementally (ISR). The built-in next-sitemap package works for static builds, but has limitations:
- ISR pages are not included until a full rebuild
- Dynamic routes require custom logic to enumerate
- Large sites (100k+ pages) hit build memory limits generating XML
- No search engine notifications -- you still need to submit to Google Search Console and Bing manually
SitemapHost handles all of this. Push your URLs via the API, and we generate, host, split, and notify search engines automatically.
Option 1: Post-Build Script
The simplest approach. After next build, run a script that collects all generated pages and pushes them to SitemapHost.
Create the script
Create a file at scripts/push-sitemap.mjs in your project root:
// scripts/push-sitemap.mjs
import fs from "fs/promises";
import path from "path";
const SITEMAPHOST_API_KEY = process.env.SITEMAPHOST_API_KEY;
const SITEMAPHOST_DOMAIN = process.env.SITEMAPHOST_DOMAIN; // e.g., sitemap.yoursite.com
const SITE_URL = process.env.SITE_URL; // e.g., https://yoursite.com
async function collectPages(dir, base = "") {
const entries = await fs.readdir(dir, { withFileTypes: true });
const urls = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const urlPath = path.join(base, entry.name);
if (entry.isDirectory()) {
urls.push(...(await collectPages(fullPath, urlPath)));
} else if (entry.name.endsWith(".html")) {
// Convert file path to URL
let loc = urlPath.replace(/\.html$/, "").replace(/\/index$/, "/");
if (!loc.startsWith("/")) loc = "/" + loc;
if (loc === "/index") loc = "/";
urls.push({
loc: `${SITE_URL}${loc}`,
lastmod: new Date().toISOString().split("T")[0],
});
}
}
return urls;
}
async function pushToSitemapHost(urls) {
const response = await fetch(
"https://dash.sitemaphost.app/api/sitemap/generate",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": SITEMAPHOST_API_KEY,
},
body: JSON.stringify({
domain: SITEMAPHOST_DOMAIN,
sizeLimit: "gsc-optimized",
urls,
}),
}
);
const result = await response.json();
if (!result.success) {
console.error("SitemapHost error:", result.error);
process.exit(1);
}
console.log(`Pushed ${urls.length} URLs to SitemapHost`);
console.log(`Sitemap: https://${SITEMAPHOST_DOMAIN}/sitemap.xml`);
}
const buildDir = path.resolve(".next/server/pages");
const urls = await collectPages(buildDir);
await pushToSitemapHost(urls);Add to your build pipeline
Update package.json:
{
"scripts": {
"build": "next build",
"postbuild": "node scripts/push-sitemap.mjs"
}
}Set environment variables in your hosting platform (Vercel, Netlify, etc.):
SITEMAPHOST_API_KEY=sk_live_xxxxxxxxxxxxxxxx
SITEMAPHOST_DOMAIN=sitemap.yoursite.com
SITE_URL=https://yoursite.comOption 2: API Route for On-Demand Updates
For sites using ISR or server-side rendering, create an API route that pushes updated URLs to SitemapHost when content changes.
Next.js App Router (app directory)
// app/api/sitemap/push/route.ts
import { NextRequest, NextResponse } from "next/server";
const SITEMAPHOST_API_KEY = process.env.SITEMAPHOST_API_KEY!;
const SITEMAPHOST_DOMAIN = process.env.SITEMAPHOST_DOMAIN!;
export async function POST(request: NextRequest) {
// Verify the request is authorized (e.g., from your CMS webhook)
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.WEBHOOK_SECRET}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { urls } = await request.json();
const response = await fetch(
"https://dash.sitemaphost.app/api/sitemap/generate",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": SITEMAPHOST_API_KEY,
},
body: JSON.stringify({
domain: SITEMAPHOST_DOMAIN,
sizeLimit: "gsc-optimized",
urls,
}),
}
);
const result = await response.json();
return NextResponse.json(result);
}Triggering from a CMS webhook
When your CMS publishes new content, call this API route:
curl -X POST https://yoursite.com/api/sitemap/push \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_WEBHOOK_SECRET" \
-d '{
"urls": [
{
"loc": "https://yoursite.com/blog/new-post",
"lastmod": "2024-01-15"
}
]
}'Option 3: Named Sitemaps for Different Content Types
For larger sites, use named sitemaps to organize URLs by content type. This gives you better Google Search Console insights.
// lib/sitemaphost.ts
const SITEMAPHOST_API_KEY = process.env.SITEMAPHOST_API_KEY!;
const SITEMAPHOST_DOMAIN = process.env.SITEMAPHOST_DOMAIN!;
type SitemapUrl = {
loc: string;
lastmod?: string;
priority?: number;
changefreq?: string;
images?: Array<{
loc: string;
title?: string;
caption?: string;
}>;
};
export async function pushSitemap(
sitemapName: string,
urls: SitemapUrl[]
) {
const response = await fetch(
"https://dash.sitemaphost.app/api/sitemap/generate",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": SITEMAPHOST_API_KEY,
},
body: JSON.stringify({
domain: SITEMAPHOST_DOMAIN,
sitemapName,
sizeLimit: "gsc-optimized",
urls,
}),
}
);
if (!response.ok) {
throw new Error(`SitemapHost error: ${response.status}`);
}
return response.json();
}Use it in your build script:
import { pushSitemap } from "../lib/sitemaphost";
// Push blog URLs
const blogPosts = await getBlogPosts(); // Your CMS query
await pushSitemap(
"blog",
blogPosts.map((post) => ({
loc: `https://yoursite.com/blog/${post.slug}`,
lastmod: post.updatedAt,
images: post.featuredImage
? [{ loc: post.featuredImage.url, title: post.title }]
: undefined,
}))
);
// Push product URLs
const products = await getProducts();
await pushSitemap(
"products",
products.map((product) => ({
loc: `https://yoursite.com/products/${product.slug}`,
lastmod: product.updatedAt,
priority: 0.8,
images: product.images.map((img) => ({
loc: img.url,
title: img.alt,
})),
}))
);ISR Considerations
If you use Incremental Static Regeneration, pages are generated on-demand and cached. This means a full build may not include all pages.
Recommended approach: Fetch URLs from your data source (CMS, database) rather than scanning the build output.
// scripts/push-all-urls.mjs
// Fetch all published content from your CMS
const allPosts = await cms.getAllPosts({ status: "published" });
const allProducts = await cms.getAllProducts({ status: "active" });
const allPages = await cms.getAllPages({ status: "published" });
// Combine and push
const urls = [
...allPages.map((p) => ({
loc: `https://yoursite.com/${p.slug}`,
lastmod: p.updatedAt,
})),
...allPosts.map((p) => ({
loc: `https://yoursite.com/blog/${p.slug}`,
lastmod: p.updatedAt,
})),
...allProducts.map((p) => ({
loc: `https://yoursite.com/products/${p.slug}`,
lastmod: p.updatedAt,
priority: 0.8,
})),
];
await pushToSitemapHost(urls);CI/CD Integration
GitHub Actions
# .github/workflows/sitemap.yml
name: Update Sitemap
on:
push:
branches: [main]
schedule:
- cron: "0 */6 * * *" # Every 6 hours
jobs:
update-sitemap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: node scripts/push-sitemap.mjs
env:
SITEMAPHOST_API_KEY: $
SITEMAPHOST_DOMAIN: sitemap.yoursite.com
SITE_URL: https://yoursite.comVercel Deploy Hook
If you deploy on Vercel, add a post-deploy script in vercel.json:
{
"buildCommand": "next build && node scripts/push-sitemap.mjs"
}Disabling Next.js Built-In Sitemap
If you were using next-sitemap or a custom sitemap.xml route, disable it to avoid conflicts:
- Remove the
next-sitemappackage if installed - Delete any
app/sitemap.tsorpages/sitemap.xml.tsfiles - Update
robots.txtto point to your SitemapHost URL:
User-agent: *
Allow: /
Sitemap: https://sitemap.yoursite.com/sitemap.xmlNext Steps
- API Reference -- Full request/response documentation
- Multi-Sitemap Support -- Organize URLs by content type
- Google Search Console -- Auto-submit sitemaps to Google