
Valid Sitemap, But No Indexing
Your sitemap works. Google fetches it without errors. Yet in Google Search Console, pages show "Discovered - currently not indexed" or never get crawled.
This is not a content quality issue. It is a sitemap architecture problem - specifically, how your sitemap handles scale and crawl prioritisation.
In this guide, you will learn how to build a production‑ready Next.js sitemap system that solves indexing delays using sitemap index files and paginated dynamic routes.
Why Google Ignores Your Pages Even With a Sitemap
In many production projects, the sitemap is valid, URLs are correct, and Google can fetch the file. Yet indexing fails because of crawl inefficiency, not visibility. The most common root causes include too many URLs packed into a single sitemap, no logical segmentation between static pages, blog posts, and products, and a complete lack of pagination strategy for large collections.
Search engines enforce strict technical limits. A single sitemap file can contain a maximum of 50,000 URLs and cannot exceed 50 MB in file size. For large websites – such as blogs with hundreds of posts or product catalogues with thousands of SKUs – a single sitemap is insufficient.
Static vs. Dynamic Sitemap in Next.js
For sites with a fixed set of pages like home, about, and contact, a static sitemap is sufficient. The following example shows a static sitemap implementation in Next.js using the MetadataRoute.Sitemap type.
Example: src/app/static/sitemap.ts
import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date(),
},
{
url: 'https://example.com/about',
lastModified: new Date(),
},
];
}For dynamic content – such as blog posts, products, or categories – the sitemap must fetch data and generate URLs asynchronously. The example below demonstrates a dynamic sitemap that fetches posts from an API.
Example: src/app/blog/sitemap.ts
import { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return posts.map((post: any) => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
}));
}
Sitemap Index Architecture
To handle scalability, you need a Sitemap Index, an XML file that references multiple child sitemaps. This is the only reliable way to ensure all your pages get crawled and indexed. The recommended folder structure:
src/app/
│
├── sitemap.xml/route.ts
├── static/sitemap.ts
├── product/sitemap.ts
├── ..... 1. Create the Sitemap Index Route
The index route calculates how many child sitemaps are needed based on total record count and a defined page size. The following implementation uses getProductCount to determine the number of product sitemaps, then generates an XML sitemap index that references a static sitemap and multiple product sitemaps.
import { NextResponse } from "next/server";
import { getProductCount } from "@/lib/api"; //write count logic according to your need
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!;
const PRODUCT_PER_SITEMAP = 1000;
export async function GET() {
const totalProduct = await getProductCount();
const totalProductSitemaps = Math.max(
1,
Math.ceil(totalProduct / PRODUCT_PER_SITEMAP)
);
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>${SITE_URL}/static/sitemap.xml</loc>
</sitemap>
${Array.from({ length: totalProductSitemaps }, (_, i) => {
return `
<sitemap>
<loc>${SITE_URL}/product/sitemap/${i + 1}.xml</loc>
</sitemap>`;
}).join("")}
</sitemapindex>`;
return new NextResponse(xml, {
headers: {
"Content-Type": "application/xml; charset=utf-8",
"Cache-Control": "s-maxage=3600, stale-while-revalidate=86400",
},
});
}2. Create a Sitemap for Static Pages
A separate sitemap for fixed, non‑dynamic pages helps Google crawl your most stable content first. The static sitemap includes URLs like the homepage and about page, with appropriate changeFrequency and priority values.
import type { MetadataRoute } from "next";
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!;
export default function sitemap(): MetadataRoute.Sitemap {
const now = new Date();
return [
{
url: `${SITE_URL}/`,
lastModified: now,
changeFrequency: "daily",
priority: 1,
},
{
url: `${SITE_URL}/about-us`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.7,
},
];
}
3. Create a Paginated Dynamic Sitemap for Products
For dynamic collections like products, you need multiple sitemap files, each containing a subset of records. Next.js provides the generateSitemaps function to create such paginated sitemaps automatically. The following code defines a constant PRODUCT_PER_SITEMAP (1000 products per file), calculates the total number of sitemaps needed, and then generates each sitemap with the appropriate product data.
import type { MetadataRoute } from "next";
import { getProducts, getProductsCount } from "@/lib/api";
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!;
const PRODUCT_PER_SITEMAP = 1000;
type Product = {
slug: string;
updatedAt?: string;
publishedAt?: string;
};
export async function generateSitemaps() {
const totalProducts = await getProductsCount();
const totalSitemaps = Math.max(
1,
Math.ceil(totalProducts / PRODUCT_PER_SITEMAP)
);
return Array.from({ length: totalSitemaps }, (_, i) => ({
id: i + 1,
}));
}
export default async function sitemap({
id,
}: {
id: Promise<number>;
}): Promise<MetadataRoute.Sitemap> {
const products = await getProducts(id, PRODUCT_PER_SITEMAP);
return products.map((product: Product) => ({
url: `${SITE_URL}/product/${product.slug}`,
lastModified:
product.updatedAt || product.publishedAt || new Date().toISOString(),
changeFrequency: "weekly",
priority: 0.8,
}));
}With this architecture, the first product sitemap (/product/1.xml) contains the first batch of up to 1000 products, the second (/product/2.xml) contains the next batch, and so on. The sitemap index automatically includes all generated child sitemaps. This approach ensures that no single sitemap exceeds search engine limits, allows Google to crawl large collections incrementally, and gives priority to static pages by listing them first.
If Google is not indexing pages after implementing the above, verify several technical conditions. First, the sitemap must return an HTTP 200 OK status. Second, the response must include the correct Content-Type: application/xml header. Third, check that your robots.txt file does not block the sitemap or any of the indexed paths. Then submit both the sitemap index and all child sitemaps via Google Search Console.
Best Practices for Production Sitemaps
It's better to split at 5,000 to 10,000 URLs per sitemap. This helps Google prioritise fresher content and recrawl more frequently. Always include the lastModified field using the most recent update timestamp from your data source, such as updatedAt or publishedAt.
Read more

10 Powerful Chrome Extensions for SEO in 2026 - Tools Every Marketer and Developer Should Know
Looking for powerful SEO tools for Chrome? Discover the best Chrome extensions for SEO analysis, keyword research, technical audits, and competitor tracking - handpicked for 2026.

What Is a Modern Developer Stack in 2026?: A Practical Guide to Building Real Production Systems
Many tutorials teach modern web stacks through simple demo projects, but real production systems require deeper understanding. This guide explains the practical skills developers need to build secure, scalable applications using Next.js, APIs, authentication, databases, and modern web architecture.

From Zero to Production: A Real-World Guide to Building and Deploying a Production-Grade Next.js Website
Many developers build beautiful Next.js projects but struggle to take them to a true production environment. This guide explains a practical architecture covering frontend, backend, database, file storage, and deployment used in real-world applications.