Time Lens
Back to all articlesSoftware Engineering5 Minutes Read

Next.js Sitemap Guide 2026: Fix Indexing Issues & Build Scalable SEO Architecture

Struggling with Next.js sitemap indexing issues? Learn how to build a scalable sitemap architecture with static, dynamic, and index sitemaps to ensure proper crawling and better SEO performance.

Next.js Sitemap Guide 2026: Fix Indexing Issues & Build Scalable SEO Architecture

Your sitemap works. Google sees it. But your pages still don’t index. Here’s why.

Your site is live, sitemap is generated… yet Google is NOT indexing your pages properly.

This is more common than you think. The issue is usually not your content - it's your sitemap architecture.

In this guide, you'll learn how to properly build a Next.js sitemap system that works in real production environments.

Real Problem:

In our production project:

  • Sitemap was valid
  • URLs were correct
  • Google could fetch it

Yet in Google Search Console:

  • Pages showed “Discovered – currently not indexed”
  • Some URLs were never crawled

What We Found

The issue wasn’t visibility — it was crawl efficiency.

  • Too many URLs in a single sitemap
  • No clear segmentation (products, static, etc.)
  • No pagination strategy

This made it harder for Google to prioritize crawling.

Common Developer Mistake

Many developers:

  • Generate a sitemap
  • Check it in the browser
  • Submit it to Google Search Console

And assume everything is done. But pages still don’t get indexed properly.

Why?

  • Poor sitemap structure
  • No scalability planning
  • Ignoring search engine limitations

Static vs Dynamic Sitemap

Static Sitemap (Simple Websites)

If your site has fixed pages like:

  • Home
  • About
  • Contact

You can create a static sitemap manually.

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(),
    },
  ];
}

Dynamic Sitemap (Real-World Use Case)

For dynamic content like:

  • Blog posts
  • Products
  • Categories

You need to fetch data and generate URLs dynamically.

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),
  }));
}
 

The Real Problem in Production

Everything works fine locally. But in production:

  • Sitemap loads correctly
  • Yet Google fails to index pages

Root Causes:

1. Sitemap Limits

  • Search engines have strict limits:
  • Maximum 50,000 URLs per sitemap
  • Maximum 50MB file size

2. Large-Scale Websites

  • E-commerce sites with thousands of products
  • Blogs with hundreds of posts

A single sitemap is not enough.

The Solution: Sitemap Index Architecture

To handle scalability, you need a Sitemap Index that references multiple child sitemaps.

App Structure

src/app/

├── sitemap.xml/route.ts 
├── static/sitemap.ts         
├── product/sitemap.ts    

1. Create Sitemap Index

import { NextResponse } from "next/server";
import { getProductCount } from "@/lib/api";
 
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. Static Sitemap

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. Dynamic Paginated Sitemap

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,
  }));
}

How Auto Scaling Works

/product/1.xml → contains first batch
/product/2.xml → next batch

And continues automatically using pagination. This ensures:

  • No sitemap exceeds limits
  • Efficient indexing

Debug Checklist

If your sitemap is not indexing:

  • Sitemap returns 200 OK
  • Correct application/xml header
  • No blocking in robots.txt
  • Submitted in Google Search Console
  • Each sitemap < 50k URLs
  • Response time < 2 seconds
  • URLs are crawlable (no noindex)

Important

  • Don’t wait for 50k URLs → split at 5k–10k
  • Always include lastModified
  • Cache sitemap response if possible
  • Avoid heavy DB/API calls inside sitemap
  • Use ISR or edge caching for performance

Next.js gives you powerful SEO tools. But without proper sitemap architecture, your content may never reach its full potential.

Tags:
Share this article