Before publishing an Astro website, compare the sitemap with the public URLs you actually intend to release, then inspect the files served by the production domain. A successful build can still produce the wrong hostname, omit on-demand pages or leave an older sitemap in front of newly published content.
These seven Astro sitemap checks focus on that gap between configuration, route inventory and delivery. They apply to the official @astrojs/sitemap integration, with separate decisions for sites that also serve runtime-generated pages. A custom sitemap endpoint needs the same eligibility and delivery checks even when its implementation differs.
The examples use an illustrative documentation site, not results from a tested deployment. If you need the broader indexing principles first, Screpy's XML sitemap guide explains what sitemap submission can and cannot accomplish.
1. Check the production origin and URL format
Start with the configuration that generates the addresses. The official integration needs Astro's site setting, and a subdirectory deployment also needs the correct base. A preview hostname or a missing path prefix can make every sitemap entry wrong while the XML remains perfectly readable.
For a small root-hosted example, the starting configuration could be:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com',
trailingSlash: 'always',
integrations: [sitemap()],
});
Merge the relevant settings into your existing configuration; do not replace adapters or other working integrations. If the package is absent, Astro supports npx astro add sitemap. Review the resulting dependency and configuration changes before building.
Now compare a generated URL with the actual page response. In this example, /guides/install/ is the intended format. If the production host redirects it to /guides/install, the sitemap and host disagree. Decide which format the site should use, then align the configuration, page canonicals and internal links with the real destination. Do not remove slashes from every URL with a string replacement before checking the host's policy.
For a site deployed below https://example.com/docs/, inspect Astro's site, base and trailingSlash options. Confirm that the resulting addresses contain /docs/ exactly once. A leading slash passed to a URL builder can reset a pathname, so verify the actual generated values rather than assume every concatenation preserves the base.
A useful acceptance record contains three strings: expected canonical URL, sitemap <loc>, and final response URL. They should describe the same page. If the first generated entry points at localhost, a preview domain or an unexpected directory, stop there and correct the URL source before reviewing individual content entries.
2. Reconcile static and on-demand route coverage
Write down the public page set before trusting the file count. Include representative URLs from each template and compare the complete published content inventory with the sitemap's page entries. An index containing one child file does not tell you how many real pages that child covers.
The Astro sitemap integration discovers generated routes during the build, including dynamic paths that are prerendered into known pages. It cannot automatically enumerate unknown parameterized routes served on demand.
| Route type | What can establish its URL set | Release check |
|---|---|---|
A prerendered /about/ page |
The build's generated routes | Confirm the generated file and its sitemap entry |
| Prerendered article paths | The published records used to generate those paths | Compare actual eligible records with page entries |
On-demand /articles/[slug] pages |
An explicit catalog from the relevant content source | Confirm how the sitemap receives those concrete URLs |
| A separate application under the same domain | That application's own URL catalog or sitemap | Verify the intended integration boundary |
A server-rendered project can still have prerendered pages. Do not infer that every server-mode deployment has an empty sitemap, or that changing the whole project to static output is the appropriate repair. Astro's on-demand rendering guide explains the route-level distinction.
Use a meaningful comparison: expected eligible URLs minus actual sitemap URLs gives the missing set; actual minus expected identifies entries requiring review. A matching total is weaker evidence because one missing article and one unwanted draft can cancel each other out.
For a fictional documentation site, check a newly published guide, an older guide, an index page and an intentionally unpublished record. If the first three load publicly but the new guide is missing from the sitemap, investigate inventory generation and update timing. Do not conclude that the page failed to publish simply because a build-generated discovery file has not caught up.
3. Exclude URLs that should not represent the site in search
Define eligibility before writing a filter. A search sitemap should describe the canonical public pages you want discovered. Drafts, future scheduled records, login-only content, removed URLs and intentionally non-indexable pages need appropriate treatment in the source inventory.
For routes covered by the integration, filter receives an absolute URL. This illustrative root-domain configuration excludes two known path groups without accidentally matching the word “account” inside an unrelated article slug:
sitemap({
filter: (page) => {
const path = new URL(page).pathname;
return !['/drafts/', '/account/'].some(
(prefix) => path === prefix.slice(0, -1) || path.startsWith(prefix)
);
},
});
This is a configuration fragment for an existing integration. Adapt the prefixes to your actual paths, including any configured deployment base. A path filter cannot inspect a draft flag stored only in a CMS record. Apply the content publication rules when generating the route catalog and any custom URL list as well.
Review representative entries against their final response and canonical. A URL that redirects to another page should normally be replaced by the destination you actually intend to advertise. Likewise, a public help screen with a deliberate noindex should not appear in the search sitemap merely because it returns 200.
Removing an entry does not make its page private or prevent indexing. Authentication protects private content. Indexing directives have a different purpose, and Google must be able to crawl a page to read its noindex instruction.
Save both an inclusion and an exclusion example when checking the output: an approved guide should remain, while a known draft URL should not appear. Broad exclusions such as “remove every URL containing a number” can silently discard legitimate guides, product pages or pagination. Match the actual publication policy instead of trying to improve a sitemap by making it smaller.
4. Check custom URLs and the update trigger
When the generated route set is incomplete, choose how the missing URLs enter the sitemap. For a small set known during the build, customPages accepts an array of absolute URL strings:
sitemap({
customPages: [
'https://example.com/reference/widget-a/',
'https://example.com/reference/widget-b/',
],
});
The two addresses are illustrative. Confirm that each real page exists, is eligible for search and uses the intended canonical format. Do not pass an asynchronous callback where the option expects an array. If your build fetches a content catalog, resolve that work into the configuration-supported value before relying on the generated file.
If a separate site section already maintains its own sitemap, customSitemaps can add that sitemap to Astro's index. This option was introduced in @astrojs/sitemap 3.5.0, so confirm your installed package before copying it. A child-sitemap URL belongs in that option; an individual page URL belongs in the page catalog. Do not swap the two.
For content that must be represented between builds, a maintained runtime sitemap endpoint may be appropriate. Its freshness still depends on the actual data source, loader and cache policy. A content collection is not automatically a live database query, and a response cache can keep an endpoint's output older than its source records.
| Publishing event | Question to resolve |
|---|---|
| A CMS record becomes public | Does that regenerate the sitemap, or only change the runtime page? |
| A scheduled publication time arrives | Is there a build or inventory refresh when eligibility changes? |
| A page is removed or renamed | When will the sitemap stop advertising the old address? |
| A runtime response is cached | When will readers receive the updated URL catalog? |
For example, a guide published at 10:00 can be live while a sitemap built at 09:00 still excludes it. Save both observations. Repair the missing update trigger rather than repeatedly submit the unchanged file to Google. Keep the simpler build-generated approach when it meets your publishing needs; not every missing entry requires replacing the integration.
5. Verify language alternates against actual translations
Treat this check as not applicable when the site has only one language. Adding invented translations or language paths to a sitemap does not create localized content.
For a simple root-hosted site with English pages and real German equivalents under /de/, the integration supports an i18n mapping such as:
sitemap({
i18n: {
defaultLocale: 'en',
locales: {
en: 'en-GB',
de: 'de-DE',
},
},
});
This is a fragment to merge into the sitemap options. The keys identify route locale segments, while the values are language annotations. The default locale must be a key in the mapping. Match the actual routing design, especially if the site uses a base path, different slugs or a nonstandard locale arrangement.
Inspect one full alternate group in the generated XML. For the illustrative /guides/install/ and /de/guides/install/ pair, both pages should identify the corresponding versions, including themselves. Each destination must load the intended translated content rather than redirect to a generic language homepage.
Google's localized-page guidance explains the reciprocal and self-referencing annotations. A translated homepage is not an equivalent for every untranslated article. If only one version of a guide exists, do not imply a second version merely because the global locale configuration lists two languages.
Also check the XML namespace needed for the emitted xhtml:link elements. If you customized namespace settings to reduce unused declarations, make sure you did not remove one the actual alternate markup requires.
Record the real source URL, language code, alternate destination and response for one complete group and one untranslated page. That sample catches a missing return link or an invented sibling more effectively than checking that the text hreflang appears somewhere in the file. For larger sites, validate the rest of the groups against the same published translation inventory.
6. Parse XML and audit dates, limits and child files
A file named sitemap.xml can still contain an HTML error page. Parse the XML and inspect its root element before counting URLs. A sitemap index lists sitemap files; a URL sitemap lists pages. They are different inventories.
Download the generated index and every child it names into one directory, preserving their filenames. Save this small validator there as check_sitemaps.py, then run python check_sitemaps.py sitemap-index.xml:
from collections import Counter
from pathlib import Path
import sys
from urllib.parse import urlsplit
import xml.etree.ElementTree as ET
NS = '{http://www.sitemaps.org/schemas/sitemap/0.9}'
seen = set()
def collect(path):
path = path.resolve()
if path in seen:
raise ValueError(f'Repeated sitemap reference: {path}')
seen.add(path)
root = ET.parse(path).getroot()
if root.tag == NS + 'sitemapindex':
urls = []
for loc in root.findall(NS + 'sitemap/' + NS + 'loc'):
name = Path(urlsplit(loc.text or '').path).name
if not name:
raise ValueError('Missing child sitemap filename')
urls.extend(collect(path.parent / name))
return urls
if root.tag != NS + 'urlset':
raise ValueError(f'Unexpected XML root: {root.tag}')
urls = []
for entry in root.findall(NS + 'url'):
value = (entry.findtext(NS + 'loc') or '').strip()
if not value.startswith(('https://', 'http://')):
raise ValueError(f'Missing or non-absolute loc: {value}')
urls.append(value)
return urls
urls = collect(Path(sys.argv[1]))
counts = Counter(urls)
print('Entries:', len(urls), 'Unique URLs:', len(counts))
for url, count in counts.items():
if count > 1:
print('Duplicate:', count, url)
Path('actual-urls.txt').write_text(
'\n'.join(sorted(counts)) + '\n', encoding='utf-8'
)
This checks downloaded, uncompressed files. Missing children and malformed XML fail visibly. It does not fetch URLs, establish indexability or validate every sitemap extension. If remote children share a filename, place them in separate directories and adapt the local mapping rather than overwrite one. Use actual-urls.txt for the inventory comparison in check 2.
Follow the real index instead of assuming every installation has only sitemap-0.xml. Custom filenameBase values change names, and the integration's chunks option, added in version 3.7.0, creates named groups with their own numbered files.
The integration's default entryLimit is 45,000. Google's separate sitemap requirements allow at most 50,000 URLs and 50 MB uncompressed per sitemap. A URL count below the threshold does not prove the file meets the size limit.
Finally, sample lastmod values against meaningful page updates. Do not label every unchanged page as modified whenever a build runs. Google ignores priority and changefreq; tuning those fields cannot repair missing URLs. An image or news namespace declaration alone also does not prove that useful extension entries were generated.
7. Verify deployed files and discovery references
The final check starts after deployment. Reading dist/ proves what a build produced, while fetching the public URL proves what visitors and crawlers can receive now. Those can differ because of deployment paths, caching or an old release.
Fetch the real sitemap index and inspect both headers and body. For the illustrative root-hosted configuration above:
curl -sS -D sitemap-headers.txt \
https://example.com/sitemap-index.xml \
-o sitemap-index.xml
Replace the domain and path with your actual configuration. This command deliberately does not hide redirects by following them automatically. Inspect any redirect and its destination, then fetch the intended final URL. Require the expected successful response and XML content, not merely a status of 200: a hosting fallback can return the site's HTML homepage for a missing XML path.
Fetch every child referenced by the index too. Confirm they belong to the intended release and contain the same canonical URL inventory you approved locally. Repeat the inventory comparison against these public files. A new index referencing stale children still fails the release check.
Check the public robots.txt and retain its existing rules. Its sitemap declaration should point to the actual deployed index, including a base path or custom filename when applicable:
Sitemap: https://example.com/sitemap-index.xml
Do not replace the entire robots file with this line. Check that your important pages remain crawlable and that exclusions have their intended effect. Sitemap discovery and permission to crawl are separate concerns.
Use a short release worksheet rather than relying on memory:
| Evidence to record | Passing result |
|---|---|
| Public index and each child | Expected response and valid XML roots |
| Expected versus actual URL inventory | Every difference explained or fixed |
| Newly published page | Present at its final canonical URL |
| Removed or redirected page | Old sitemap entry removed or corrected |
| Sample page responses and directives | Intended content, canonical and indexability |
| Translation group, if applicable | Real equivalent pages with correct return annotations |
| Release identifier and check time | Evidence tied to the deployed version |
Keep the worksheet's actual observations separate from these expected results. A template is not evidence that a site passed.
A crawl can help check page responses, canonicals and link discovery alongside this sitemap review. Screpy Website Audit is one option; this article is published by Screpy. Compare the crawl's URL inventory with the sitemap, while treating Google's indexing decisions as a separate question.
After the public files pass, submit the correct sitemap in Search Console when appropriate. For an individual new page, URL Inspection's Request indexing is a separate action. Google's URL Inspection documentation distinguishes available inspection and request workflows. An accepted request does not guarantee indexing or rankings, and repeated requests do not repair an outdated sitemap.
These checks use Astro's integration documentation and Google's guidance reviewed on October 17, 2026. The integration page identified version 3.7.4. The configurations and small site examples illustrate verification steps; they are not results from a deployed test site. Recheck the installed package's supported options before applying version-specific settings.