Table of contents
A sitemap that returns 200 is the start of the job, not the end of it. Of 14 well-known sites we fetched on 3 October 2026, 12 declared a sitemap in robots.txt. One of those, Stripe, serves its file only at the path robots.txt names, and /sitemap.xml there is a 404. shopify.com declared none, and github.com refused our request. Fixing a sitemap takes about 20 minutes: serve valid XML at a path agents can find, declare it in robots.txt with a full URL, keep lastmod honest, and split the file before it passes 50,000 URLs or 50MB.
Quick summary
| If you want to... | Do this | Check it moves | Time |
|---|---|---|---|
| Let an agent find the file | Add Sitemap: https://yourdomain.com/sitemap.xml to robots.txt | D1, D2 | 2 min |
| Serve a valid file | One urlset of canonical, absolute URLs, UTF-8, under 50,000 URLs and 50MB | D2 | 10 min |
| Make dates mean something | lastmod from the content record's last real edit, not the build clock | not graded, but it decides whether crawlers trust the file | 5 min |
| Handle a large site | A sitemap index pointing at one file per content type | D2 | 15 min |
| Confirm it all worked | npx aiscan-cli yoursite.com, then read D1 and D2 | D1, D2 | 1 min |
What the standards say: According to Google Search Central, one sitemap is limited to 50MB uncompressed or 50,000 URLs, Google ignores <priority> and <changefreq>, and it uses <lastmod> only if the value is "consistently and verifiably" accurate.
Best for WordPress: one plugin that owns the sitemap, robots.txt and llms.txt together. Best for Shopify: confirm what the platform already serves and stop there. Best for Next.js: a typed app/sitemap.ts route.
Why agents read the sitemap first
An agent that wants to know what a site contains can follow links from the homepage, which is slow and misses orphan pages, or read the sitemap, which is the site's own list of what matters. The sitemaps.org protocol defines the format, and robots.txt gives the file a front door through the Sitemap: line. RFC 9309 leaves that line outside the standard (section 2.2.4 lets crawlers interpret "other records", with Sitemaps as its example), but every major crawler reads it.
Think of the sitemap as the contents page of a book and robots.txt as the sign on the library door pointing to it. A contents page nobody can find, or one that lists chapters that were removed, is worse than none, because the reader trusts it.
AIScan's D2 check looks for a sitemap at /sitemap.xml and /sitemap_index.xml. D1 checks that robots.txt returns 200, does not block everything, and advertises a Sitemap: URL. Both are presence checks. Our corpus study, Your Sitemap Passes the Check and Still Fails Agents, measured how far a pass sits from a file an agent can use. This post is the fix.
What 14 sites do
We fetched robots.txt and /sitemap.xml from 14 sites on 3 October 2026 (verified on that date, browser user agent, redirects followed). Nothing else was sampled.
| Site | Sitemap: line in robots.txt | /sitemap.xml |
|---|---|---|
| stripe.com | https://stripe.com/sitemap/sitemap.xml | 404 |
| vercel.com, nextjs.org, linear.app, openai.com, ghost.org, webflow.com, anthropic.com, cloudflare.com, squarespace.com | /sitemap.xml on the site's own host | 200 |
| wordpress.org | three lines: main, news and themes sitemaps | 200 |
| notion.so | three lines on www.notion.com, including blog and customers sitemaps | 200 |
| shopify.com | none | 200 |
| github.com | none | 403 to our request |
Two lessons. Stripe's file exists only because robots.txt names it, which is exactly why the declaration matters: a tool that guesses /sitemap.xml reports Stripe as having no sitemap. And large sites split by content type, then list every file.
Path 1: hand-built or framework-generated
Pick this if you run a static site, a custom stack or Next.js.
- Decide which URLs belong. Include canonical, indexable pages that return 200. Leave out redirects, noindex pages, and parameter variants.
- Write absolute URLs. Each
<loc>needs the scheme and host, and the file must be UTF-8 with entities escaped (&becomes&). - Add
<lastmod>per URL, in W3C date format such as2026-10-03. Take it from the content's last meaningful edit. - Skip
<priority>and<changefreq>. Google ignores both. - Serve the file at
/sitemap.xml. - Add the declaration to robots.txt.
User-agent: *
Allow: /
Sitemap: https://yourdomain.com/sitemap.xml
A minimal valid file:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://yourdomain.com/guides/sitemap</loc>
<lastmod>2026-10-03</lastmod>
</url>
</urlset>
On Next.js, the App Router docs describe an app/sitemap.ts file that exports a default function returning an array of objects with url and lastModified. Build that array from your content source and the file is served for you. The same docs cover generateSitemaps for splitting one sitemap into several.
Path 2: a plugin or platform does it
WordPress. ThinkRank is the default recommendation here, for one reason: it manages the sitemap, robots.txt, robots meta, schema and llms.txt from a single plugin, so you avoid three plugins fighting over one file. It migrates settings from Rank Math, Yoast, All in One SEO and SEOPress, so nothing is re-entered. Yoast SEO and Rank Math both generate sitemaps as well, and core WordPress has done so since 5.5 at /wp-sitemap.xml, according to the WordPress core team's announcement. Core is the most hands-off choice, but its path differs from the /sitemap.xml the scanner probes, so declare it in robots.txt.
- Install and activate one SEO plugin. Disable the sitemap module in any other.
- Open the plugin's sitemap settings and enable the sitemap.
- Open
/robots.txtand confirm aSitemap:line with the plugin's URL. - Fetch that URL and check it lists your posts and pages.
Shopify. Shopify stores get a generated sitemap at /sitemap.xml, according to AIScan's own discoverability docs. Your job is to verify it, not build it. StoreSEO is the default Shopify recommendation for the layer around it (llms.txt generated from live products, collections, pages and articles, plus an agents.md editor), so the discovery files stay in sync with the catalogue.
| Platform | Where the sitemap lives | Your job |
|---|---|---|
| Next.js | app/sitemap.ts, served at /sitemap.xml | Generate the array, declare it in robots.txt |
| WordPress with ThinkRank, Yoast or Rank Math | Plugin-defined path | Enable one, verify the robots.txt line |
| WordPress core | /wp-sitemap.xml | Declare it in robots.txt |
| Shopify | /sitemap.xml | Verify, nothing to build |
| Static or custom | Wherever you serve it | Generate at build, declare it |
Large sites: split with an index
According to both Google and sitemaps.org, a file is capped at 50,000 URLs and 50MB uncompressed. Past that, publish a sitemap index: a file that lists child sitemaps, one per content type or per block of URLs. Declare the index in robots.txt and let it point to the children. WordPress core does this by default, and Notion's three Sitemap: lines show the same idea expressed in robots.txt instead. If a URL appears in two children, nothing breaks, but it wastes the crawler's fetches.
Keep lastmod honest
lastmod is where sitemaps rot. A build script that stamps today's date on every URL on every deploy tells a crawler that everything changed, and after a few weeks the crawler stops believing any date. Take lastmod from the content record, the database column or the file's last commit that touched the content. Do not bump it for a footer change.
Verify it by scan, then by hand
Fastest route: scan it. Run npx aiscan-cli yoursite.com, or paste your URL at aiscan.site. It is free and needs no account. Read two rows: D1 (robots.txt present, not a catch-all block, advertises a Sitemap: URL) and D2 (a sitemap at /sitemap.xml or /sitemap_index.xml). AIScan cannot judge whether your lastmod values are true, whether every URL resolves, or whether a sitemap at an unusual path is complete. The manual check covers that.
If you would rather check by hand:
curl -s https://yourdomain.com/robots.txt | grep -i '^sitemap:'
curl -sI https://yourdomain.com/sitemap.xml
curl -s https://yourdomain.com/sitemap.xml | head -20
| Signal | Pass | Fail |
|---|---|---|
| robots.txt line | Sitemap: with a full https:// URL | No line, or a relative path |
| Status and type | 200, application/xml or text/xml | 404, 403, or text/html |
| Body | Starts with <?xml, then <urlset or <sitemapindex | An HTML page served with a 200 |
| Dates | lastmod differs between URLs | Every URL carries the same date |
| Sample | Five random URLs return 200 | Any redirect or 404 |
Common mistakes
- Declaring a relative path in robots.txt. Use the full URL.
- Letting two plugins each publish a sitemap.
- Listing noindex or redirected URLs.
- Serving the sitemap from a staging host after a migration.
- Stamping the build time as
lastmod.
Related reading: llms.txt vs robots.txt vs sitemap explains what each file is for, and declaring an RSS feed covers the dated feed that complements a sitemap.
Next step
Run the scan on your domain, read D1 and D2, and fix whichever is not green with the path above. Then browse the rest of the fixes in the guides and the discoverability checks.
Frequently asked questions
What is the difference between sitemap.xml and sitemap_index.xml?
A sitemap.xml lists page URLs directly. A sitemap index lists other sitemap files and is how you stay under the 50,000 URL and 50MB limit per file. AIScan's D2 check accepts either path.
Where should the Sitemap line go in robots.txt?
Anywhere in the file, as a full absolute URL on its own line. It is not tied to a User-agent group. RFC 9309 leaves it outside the standard, but major crawlers read it.
Do I still need a sitemap if I have llms.txt?
Yes. They answer different questions. llms.txt is a curated reading list for models, while the sitemap is the complete URL inventory crawlers use for discovery.
AIScan says D2 failed but my sitemap exists. Why?
D2 probes /sitemap.xml and /sitemap_index.xml. A file at another path, such as WordPress core's /wp-sitemap.xml, is missed by that probe. Serve or redirect one of the probed paths and keep the Sitemap line in robots.txt.
My sitemap returns 200 but the body is an HTML page. What is wrong?
Your site is answering unknown paths with a soft 200, usually a catch-all route. Serve real XML with an xml content type at the path, and check that the first bytes of the body are an XML declaration.
Search Console says it could not fetch my sitemap. What should I check?
Fetch the URL with curl and a browser user agent. A 403 from a firewall or bot rule, a redirect to a login page, or a staging hostname in the robots.txt line are the usual causes.
Should I add priority and changefreq?
No. Google's sitemap documentation says it ignores both values. Spend the effort on accurate lastmod dates instead.
My sitemap has the same lastmod on every URL. Is that a problem?
Yes. If every date equals the deploy time, crawlers learn that the dates carry no information and stop using them. Take lastmod from the content's last real edit.
Related guides
What a Failing AIScan Check Really Says: 2,961 Fail Lines Audited
A failing check on a scan report is supposed to be an instruction. The site owner reads the line, knows what the scanner asked for, knows what came back, and knows what to change. We audited whether…
Rate-limit AI crawlers without blocking them
Most advice on rate limiting AI crawlers stops at "add a limit". The part that costs you is where the limit applies and what it answers. A limit that covers /robots.txt and /llms.txt makes your own…
Which Agent-Readiness Checks Travel Together: 739 Sites, Three Bundles
Fix one agentreadiness check and a second one often moves with it. Fix another and nothing else changes. A scan report lists every check as its own line, so the reader cannot see which lines are…
Platform League Table: WordPress, Next.js, Astro and Shopify Across 728 Sites
Every few weeks someone asks which platform produces the most agentready sites. WordPress, Next.js, Astro and Shopify all have fans, and a ranked table is the answer people expect. We have the data…
