Skip to content

Sitemaps for Next.js Sites: The Pattern That Keeps Google Indexed

Sitemaps aren’t optional. Here’s the pattern that ships with every FH client build.

John Cravey with AIFounder5 min readUpdated Jul 6, 2026

A sitemap tells Google every URL on your site that should be indexed. Without one, Google still finds most of your pages eventually — via internal links and external backlinks. With one, Google finds them in days instead of weeks, and the indexed-page count stays close to your published-page count instead of drifting. Every FH client site ships with an auto-generated sitemap. Here’s the pattern.

Free estimate · 2 minutes

Your Search Console data already shows the gap.

The estimate doubles as intake for an audit: we read your data, show where the clicks are being lost, and price the fix.

What a sitemap is (and isn’t)

It’s an XML file listing every URL on your site, with optional metadata: last modified, change frequency, priority. Crawlers (Google, Bing, anyone) read it to discover URLs they haven’t crawled yet and to re-prioritize URLs that recently changed.

It is not a ranking signal. URLs in a sitemap don’t rank better than URLs not in a sitemap. The sitemap only affects discovery and crawl prioritization. The ranking work is on-page SEO, content, and links.

Next.js App Router: app/sitemap.ts

Next.js generates a sitemap from a TypeScript file at `app/sitemap.ts`. Export a default function that returns an array of URL objects. Next handles the XML serialization, the routing (`/sitemap.xml`), and the caching.

// app/sitemap.ts
import type { MetadataRoute } from "next";
import { POSTS } from "@/lib/blog/posts";
import { LOCATIONS } from "@/lib/locations";
import { SERVICES } from "@/app/components/home/data";
import { slugify } from "@/lib/slug";

const BASE = "https://frontendhorizon.com";

export default function sitemap(): MetadataRoute.Sitemap {
  const now = new Date();
  const staticRoutes = [
    "",
    "/solutions",
    "/who-we-serve",
    "/portfolio",
    "/blog",
    "/contact",
  ].map((path) => ({
    url: `${BASE}${path}`,
    lastModified: now,
    changeFrequency: "monthly" as const,
    priority: 0.8,
  }));

  const posts = POSTS.map((p) => ({
    url: `${BASE}/blog/${p.slug}`,
    lastModified: new Date(p.updatedAt ?? p.publishedAt),
    changeFrequency: "monthly" as const,
    priority: 0.6,
  }));

  const locations = LOCATIONS.map((l) => ({
    url: `${BASE}/locations/${l.slug}`,
    lastModified: now,
    changeFrequency: "monthly" as const,
    priority: 0.7,
  }));

  const solutions = SERVICES.map((s) => ({
    url: `${BASE}/solutions/${slugify(s.name)}`,
    lastModified: now,
    changeFrequency: "monthly" as const,
    priority: 0.7,
  }));

  return [...staticRoutes, ...solutions, ...locations, ...posts];
}

Submitting to Search Console

GSC → Sitemaps → enter the URL → Submit. Google fetches it within an hour or so and starts crawling. Status: ‘Success’ means it parsed cleanly. ‘Couldn’t fetch’ usually means a 404 or 5xx; check the URL is live.

What lastModified actually does

Google uses lastModified to prioritize re-crawls. A URL with a recent lastModified gets crawled sooner. Don’t lie about it — Google has signals to detect when you’re claiming a recent modification on a page that hasn’t actually changed, and it stops trusting your sitemap.

Sitemap size limits

Each sitemap file can hold up to 50,000 URLs or 50MB uncompressed. Above that, use a sitemap index — a master sitemap that lists multiple sub-sitemaps. Most SMB sites are nowhere near these limits. We hit them once on a retail client with 80,000 product pages; the fix was a paginated sitemap.

// app/sitemap.ts — paginated
export function generateSitemaps() {
  // Split into chunks of 5000 URLs per sitemap
  return Array.from({ length: 17 }, (_, i) => ({ id: i }));
}

export default function sitemap({ id }: { id: number }): MetadataRoute.Sitemap {
  const start = id * 5000;
  const end = start + 5000;
  return PRODUCTS.slice(start, end).map((p) => ({
    url: `https://example.com/products/${p.slug}`,
    lastModified: new Date(p.updatedAt),
  }));
}

Sitemap and robots.txt

Reference the sitemap from robots.txt so other crawlers find it. Next.js has `app/robots.ts` for this:

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

export default function robots(): MetadataRoute.Robots {
  return {
    rules: { userAgent: "*", allow: "/", disallow: ["/admin/", "/api/"] },
    sitemap: "https://frontendhorizon.com/sitemap.xml",
  };
}

Common mistakes that hurt indexing

  • Including non-canonical URLs (e.g., URLs with query strings, paginated URLs). Only include canonical URLs in the sitemap.
  • Including pages with `noindex` directives. Google will see the contradiction and ignore the sitemap entry.
  • Including pages that return 404s. The sitemap’s job is to tell Google about pages that exist.
  • Including pages blocked in robots.txt. Pick one or the other — block in robots OR include in sitemap, not both.
  • Forgetting to update the sitemap after a site rebuild. We’ve seen this 3 times; the rebuild changes URL patterns and the old sitemap is stale.

Multiple sitemaps for different content types

Some teams split sitemaps by content type: `sitemap-blog.xml`, `sitemap-products.xml`, `sitemap-locations.xml`. This is fine; it makes the GSC Coverage report easier to read because indexed counts are grouped by content type. We do it for sites with more than 5,000 URLs.

Dynamic generation and ISR

Next.js generates the sitemap at build time by default. If you publish blog posts via static generation, the sitemap rebuilds on every deploy. If you publish content out-of-band (a CMS, a database write), the sitemap needs ISR to pick up new entries without a redeploy.

// Force ISR on the sitemap
export const revalidate = 3600;  // re-generate hourly

News sitemaps and video sitemaps

If you publish news content, Google supports a special news sitemap format with extra metadata (publication date, title). If you publish video, a video sitemap helps Google index thumbnails and durations. Most SMB sites need neither. We’ve added a news sitemap exactly once for a hyperlocal news client.

Verifying it’s working

  1. Fetch your sitemap URL in a browser — should return XML.
  2. GSC → Sitemaps → status should be ‘Success’ and ‘Discovered URLs’ should match your expected page count.
  3. GSC → Pages → Indexed should grow toward your sitemap URL count over the next week or two.
  4. If indexed count stays well below sitemap count, look at the Coverage report’s Excluded reasons for why.

How this lands across FH client work

Every FH client site ships with an auto-generated `app/sitemap.ts` that pulls from the same typed data sources the site renders from. No drift between published pages and sitemap. No manual maintenance. Submitted to GSC on day one of every launch. If your site’s sitemap is hand-maintained or missing entirely, book a consultation — we’ll add the auto-generation pattern in a half-day engagement.

Answers

Frequently asked questions

What does a sitemap actually do?

It tells search engines which URLs exist and when they last changed. It is a discovery aid, not a ranking factor and not a guarantee of indexing. A page in a sitemap that is thin or blocked still will not be indexed, which is why sitemap problems and indexing problems get confused.

How should a sitemap be generated in a Next.js app?

From the same data that generates the routes, using the framework's sitemap convention, so it cannot drift. A hand-maintained sitemap is wrong the day someone adds a page and does not update it, which is always. Generating from the source of truth makes drift structurally impossible.

What should the last-modified date be?

The date the content actually changed, not the build time. Stamping every entry with the deploy timestamp tells crawlers that your entire site changed on every deploy, which is both false and useless, and it trains them to ignore the field.

Should every page be in the sitemap?

Only pages you want indexed. Thin pages, filtered variants, paginated duplicates, and anything noindexed should be left out. A sitemap listing pages you do not want indexed sends a mixed signal and makes the indexing report harder to read.

How large can a sitemap be?

Fifty thousand URLs or fifty megabytes uncompressed, beyond which you split into multiple sitemaps behind an index file. Most business sites will never approach that, but a programmatic site can, and hitting the limit silently truncates rather than erroring.

Does a sitemap guarantee indexing?

No. It helps discovery, particularly for pages with few internal links, and it does nothing for a page Google has decided is not worth storing. If pages are being crawled and not indexed, the sitemap is not the problem and adding them again will not help.

How do I know the sitemap is working?

The sitemaps report shows what was submitted, what was discovered, and any errors. The number to compare is submitted against indexed. A large gap points at content quality or technical exclusion rather than at the sitemap itself.

Should the sitemap be referenced in robots.txt?

Yes, it is one line and it lets any crawler find the sitemap without being told. Submitting it in Search Console covers Google; the robots reference covers everything else, including the AI crawlers you want reading you.

What about a separate sitemap for images or news?

Only if you have a genuine reason: significant image search value or actual news content. For most business sites they add maintenance and no benefit. The default should be one sitemap generated from the routes, split only when size forces it.

How often should the sitemap be regenerated?

On every build, which is what generating it from route data gives you for free. The alternative, regenerating on a schedule or by hand, means the sitemap describes the site as it was at some past moment, and nothing reports the drift.

Do I need to resubmit after changes?

No. Once submitted, the sitemap is fetched periodically. Resubmitting after every deploy is unnecessary. What is worth doing is checking the report occasionally to confirm the fetch is still succeeding, because a broken sitemap fails quietly.

What is the most common sitemap mistake?

Including URLs that redirect, that are noindexed, or that no longer exist. Each one is a wasted crawl and a small signal that the sitemap is not trustworthy. Generating from real route data rather than from an old list prevents all three.

Question we did not answer? Ask us directly and we will answer it here.

John Cravey, Founder
Written by
John Cravey
Founder

Founder of Frontend Horizon. Writes most of the long-form work on the FH blog.

Newer post
View Transitions API & Scroll-Driven Animations
Older post
AI Chat for Customer Service on SMB Sites: When It Helps and When It Hurts
Keep reading

More from the blog

Search Console·4 min

Schema Markup for SMB Sites: The Three Types That Actually Help

Schema is one of the highest-ROI SEO investments. Three types cover 90% of the value.

SEO·5 min

How to Find Keywords for SEO: A Working Process That Does Not Need Enterprise Tools

Keyword research fails two ways: guessing with no data, or drowning in a tool export nobody actions. The fix is a small process biased toward searches you can verify and win.

SEO·5 min

How to Do an SEO Audit: The Checklist We Run on Every New Site

A tool can score your site in ninety seconds. A diagnosis tells you which three of the two hundred flagged items actually move revenue.