AgentScan logoAgentScan
Guides7 min read

sitemap.xml in Next.js: a production checklist

Generate a sitemap that search engines and AI agents trust. Stable lastmod, correct canonicals, sitemap indexes, and verification that won't lie to you.

sitemap.xml in Next.js: a production checklist

sitemap.xml in Next.js: a production checklist

A bad sitemap quietly degrades your indexing for months. The most common failure modes are not syntax errors — they are subtle quality issues that cause search engines to deprioritize your sitemap. This is the checklist for shipping a sitemap that works.

The Next.js convention

In the App Router, prefer app/sitemap.ts over a static file. The function returns a typed array and Next handles the XML serialization, content-type header, and caching.

// app/sitemap.ts
import type { MetadataRoute } from "next";

const BASE_URL = "https://yourdomain.com";
const BUILD_DATE = new Date("2026-05-13T00:00:00Z");

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: `${BASE_URL}/`,
      lastModified: BUILD_DATE,
      changeFrequency: "weekly",
      priority: 1,
    },
    {
      url: `${BASE_URL}/blog`,
      lastModified: BUILD_DATE,
      changeFrequency: "weekly",
      priority: 0.9,
    },
  ];
}

That gives you /sitemap.xml for free, with the right content type and sitemap.org schema.

Mistakes that quietly hurt indexing

lastModified: new Date() on every URL

The most common bug. If you set lastModified to a fresh Date() per request, every URL appears modified on every fetch. Search engines respond by treating the entire sitemap as low-signal churn.

Fix: use a stable build date constant.

const BUILD_DATE = new Date("2026-05-13T00:00:00Z");

For per-page accuracy, store a real updatedAt per page (in your CMS or markdown frontmatter) and use it. Never use new Date() directly.

Listing redirected URLs

A /old-slug that 301s to /new-slug should not be in the sitemap. List the destination only.

Listing noindex pages

If a page has <meta name="robots" content="noindex">, it should not appear in the sitemap. The two signals contradict each other and Google logs the conflict.

Listing 404s

A sitemap with broken URLs gets demoted as a whole. Audit before every release.

Mixing canonical hosts

Pick one. www or no-www. http or https. The whole sitemap must use the same host as the canonical URLs on the pages.

When to split into a sitemap index

Hard limits per sitemap:

  • 50,000 URLs
  • 50 MB uncompressed

If you approach either, split:

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://yourdomain.com/sitemaps/posts.xml</loc>
    <lastmod>2026-05-13</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://yourdomain.com/sitemaps/products.xml</loc>
    <lastmod>2026-05-13</lastmod>
  </sitemap>
</sitemapindex>

In Next.js App Router, use a dynamic sitemap with id segments:

// app/sitemap.ts
export async function generateSitemaps() {
  return [{ id: 0 }, { id: 1 }];
}

export default async function sitemap({ id }: { id: number }) {
  // return MetadataRoute.Sitemap based on id
}

For most sites under 10,000 pages, do not bother. A single sitemap is simpler and cheaper to maintain.

Reference the sitemap from robots.txt

A sitemap that search engines have to guess about is a sitemap that takes longer to discover. Add the line:

Sitemap: https://yourdomain.com/sitemap.xml

Generate the matching robots.txt with the robots.txt Generator.

Submit to the search consoles

Both major engines accept submission:

Submit once. Both consoles will refetch your sitemap on their own schedule afterward.

changefreq and priority: do they matter?

Officially, Google ignores both. In practice, both engines occasionally use them as weak hints. Cost-benefit:

  • Include changeFrequency if you can compute it accurately. Default to weekly for evergreen and daily for news. Skip if you would just guess monthly.
  • Include priority only if your site has a clear hierarchy. Otherwise omit.

Neither is worth blocking on. The two signals that actually move the needle are loc (correct canonical) and lastModified (real and stable).

Validation

Run two checks before deploying.

  1. Local: paste your sitemap into the sitemap validator. It checks structure, namespace, URL count, byte size, dates, changefreq enum, priority range, and per-URL field issues.
  2. Live: after deploy, confirm with curl.
curl -I https://yourdomain.com/sitemap.xml

You should see HTTP/2 200 and content-type: application/xml.

What we ship at AgentScan

For reference, this site uses a single sitemap built from a constant build date plus per-blog-post publishedAt for lastModified. The full source is straightforward enough that we never bother with a sitemap index. Total URL count is well under 100. The discipline matters more than the cleverness.

Build yours

Use the sitemap.xml Generator to scaffold a starting body, then move it into app/sitemap.ts and replace the static date with your real build date constant.