Skip to content
Neaptidestudio
blog

Next.js Sitemap 404: Four Dev and Production Checks

Neaptide · October 13, 2026 · 7 min read

A reproducible Next.js 15.5.22 experiment separates a missing sitemap route, a failed data source and a 404 inside valid XML. Includes fixes and raw results.

On this page
Three observed sitemap failure patterns: route 404, source-triggered 500 and a listed-page 404 inside a 200 sitemap.

Before changing your Next.js sitemap configuration, identify the URL that failed. The sitemap endpoint, the data source used to generate it and a product URL inside the XML can fail independently. A screenshot of a 404 page does not tell you which layer needs a fix.

Neaptide tested four controlled cases on October 12, 2026. The wrong sitemap address returned 404; an unavailable source produced a 500 because our fetch handler explicitly threw an error; a sitemap containing a retired product returned 200 while that product returned 404. Each correction worked in both next dev and a local production build. We observed no dev-versus-production difference in these cases.

What we tested before writing this guide

The fixture uses Next.js 15.5.22, React and React DOM 19.2.6, Node v25.2.1 and two fictional products. We froze the protocol, code, collector and package lock before measurement, installed dependencies in an isolated directory and ran next dev alongside next build followed by next start. A local HTTP feed supplies the product records. The production build is local; it does not test Vercel.

Four cases produce seven variants: one healthy control and three before/after pairs. Two environments and two rounds yield 28 sitemap responses. We also requested every listed URL, giving 36 page responses, and checked the feed twice. These are 66 repeated HTTP observations, not 66 unique URLs. We saved bodies, headers and hashes. XML checks cover parsing and the urlset namespace, not full XSD validation.

The sitemap status alone missed one failure

Measured sitemap and listed-page status codes for the control and three before/after cases; dev and production matched.
Neaptide lab, October 12, 2026. Each row represents four matching observations: two environments × two rounds. A dash means no sitemap urlset was parsed, not zero URLs. Listed statuses follow the XML order.
Neaptide lab, October 12, 2026. Each row represents four matching observations: two environments × two rounds. A dash means no sitemap urlset was parsed, not zero URLs. Listed statuses follow the XML order.
CaseStateSitemap HTTPURLs in XMLListed-page HTTP
Static controlControl2002200 / 200
Dynamic sourceBefore500——
Dynamic sourceAfter2002200 / 200
Requested routeBefore404——
Requested routeAfter2002200 / 200
Retired productBefore2002200 / 404
Retired productAfter2001200

A source 404 became a sitemap 500 in our handler

The dynamic sitemap fetches a controlled JSON feed. Before correction, its source path was /missing-catalog.json, which returned 404. The handler below checks response.ok and throws. That exception produced a 500 response at /dynamic-bad/sitemap.xml. This result follows our error handling; it is not a rule that Next.js converts every upstream 404 into 500.

// lib/catalog.js
export async function fromFeed(path) {
  const response=await fetch(`http://127.0.0.1:8800${path}`,{cache:'no-store'});
  if(!response.ok) throw new Error(`Controlled catalog source returned HTTP ${response.status}`);
  const rows=await response.json();return entries(rows.map(row=>row.slug));
}

Changing the feed path to /catalog.json was sufficient: /dynamic-ok/sitemap.xml returned 200 with two reachable products. Both variants already used force-dynamic and cache: 'no-store'. Adding dynamic rendering was not the tested fix. In your application, record the upstream status and inspect server logs before changing cache settings.

// app/dynamic-ok/sitemap.js
import {fromFeed} from '../../lib/catalog';
export const dynamic='force-dynamic';
export default async function sitemap(){return fromFeed('/catalog.json');}

Check the path generated by the file location

Our nested file app/catalog/sitemap.js produced /catalog/sitemap.xml. Requesting /sitemaps/catalog.xml returned 404. Requesting the actual route returned 200; no sitemap code changed. The root app/sitemap.js control also returned 200. These paths match the file convention in the Next.js documentation. Sharded generateSitemaps routes were outside this test.

app/sitemap.js          → /sitemap.xml
app/catalog/sitemap.js  → /catalog/sitemap.xml

A 200 sitemap can still contain a 404

The stale-before variant exported every editorial record, including /products/retired. The XML parsed and the sitemap returned 200, but the retired product returned 404. Filtering by published before generating URLs left one entry, /products/widget-a, which returned 200. We removed the unpublished record; we did not redirect it or substitute a different product.

// app/stale-after/sitemap.js
import {editorialRecords,entries} from '../../lib/catalog';
export default function sitemap(){return entries(editorialRecords.filter(row=>row.published).map(row=>row.slug));}

A published flag alone is not a complete indexability rule. For a real catalog, also verify that the selected URLs are the intended canonical pages and are eligible for indexing. Our fixture pages deliberately use noindex/nofollow: this experiment checks route availability, not search eligibility.

Follow the failing URL through the system

Start with the exact submitted sitemap URL and a GET request. Save the status and body before following redirects. The decision path below is a practical checklist; the invalid-XML and hosting-only branches are recommendations, not additional measured cases.

Diagnostic path: sitemap 404 leads to route checks, 5xx to source and logs, and 200 to XML parsing followed by listed-URL checks.
Diagnostic guide informed by the measured cases. A 200 response still requires XML and listed-URL checks; hosting-specific causes were not tested.
  1. Sitemap returns 404: compare the requested path with the actual route and file location. In S3, correcting the address resolved the error.
  2. Sitemap returns 5xx: inspect the route's server error and its source response. In S2, the source was missing and the handler threw; correcting that source path resolved the failure.
  3. Sitemap returns 200: parse the body and inspect the root namespace. If it is HTML or malformed XML, investigate the response-producing route. We did not inject that case.
  4. XML parses: request the loc URLs and inspect their statuses. In S4, this revealed the retired product that a sitemap-only check missed.
  5. Failure occurs only on your hosted deployment: compare the same commit, request path, environment variables and source reachability with next build plus next start. Then examine deployment routing and logs. Our local experiment did not isolate any hosting defect.

Google recommends absolute URLs and the preferred pages you want in search results. Submitting a sitemap is a hint, not a guarantee of crawling. After deployment, use Search Console's Sitemaps report to inspect processing errors; a successful local response cannot establish Google's result.

Repeat the checks with the same fixture

Download the reproduction archive and follow README.txt. Start the local feed, install the pinned dependencies, then start dev and production on ports 8801 and 8802. The collector saves the full matrix without following redirects or crawling external sites. We verified the archive against the frozen source and replayed its collector against the original running servers: all 28 sitemap signatures matched. That replay was not a second clean server installation.

Methods, data and runnable example

These deliberately injected faults are useful for diagnosis, but say nothing about how common each error is. We did not test a live CMS, cache invalidation, middleware, rewrites, a CDN or Vercel, nor measure indexing, rankings or AI citations. A linked historical Next.js RC/Windows/Prisma issue helped frame the question; this fixture does not reproduce or resolve that environment.

faq

The short version

Should I add force-dynamic to fix a sitemap 404?

First identify the failing URL and response. Both dynamic variants in this experiment already used force-dynamic; the fix was the source path. The wrong sitemap endpoint was fixed by requesting the actual route. This study did not test cache invalidation.

Does a 200 sitemap mean every listed page works?

No. Our stale sitemap returned 200 and parsed as XML, yet one listed product returned 404. Check the listed URLs separately. Even a listed page that returns 200 still needs canonical and indexability checks on a real website.