koala73/worldmonitor · error · Error
invalid sitemap location: ${location}
Error message
invalid sitemap location: ${location} What it means
getPublishedBatches in the IndexNow submit script crawls the published sitemap tree starting from known locations. Each discovered location must be a same-origin URL with no credentials, query string, or hash, ending in .xml; anything else throws 'invalid sitemap location: <location>' to stop following malicious or malformed links found inside sitemap documents.
Solutions
- Fix the sitemap generation so all sitemap <loc> URLs are same-origin absolute https URLs ending in .xml with no query/hash
- Compare the origin in INDEXNOW_BATCHES config with the origins actually emitted in sitemaps and align them
- If a discovered URL is an HTML page, exclude it — only sitemapindex/urlset documents belong in the tree
- Check for redirect chains: fetch the canonical sitemap URL and use the final same-origin URL
Example fix
// before <loc>https://cdn.example.com/sitemap-pages.xml?ts=123</loc> // after <loc>https://example.com/sitemap-pages.xml</loc>
Defensive patterns
Strategy: validation
Validate before calling
const u = new URL(loc);
const isValidSitemapLocation = u.origin === ORIGIN && !u.username && !u.password && !u.search && !u.hash && u.pathname.endsWith('.xml');
if (!isValidSitemapLocation) skip(loc); Type guard
const isSafeSitemapUrl = (loc, origin) => {
try {
const u = new URL(loc);
return u.origin === origin && !u.username && !u.password && !u.search && !u.hash && u.pathname.endsWith('.xml');
} catch { return false; }
}; Try / catch
try {
await getPublishedBatches();
} catch (err) {
if (err.message.startsWith('invalid sitemap location:')) {
logger.error({ location: err.message.split(': ').pop() }, 'off-origin or malformed sitemap URL in published tree');
} else throw err;
} Prevention
- Generate sitemap <loc> URLs from a single canonical origin constant
- Never append query strings or fragments to sitemap URLs
- Periodically audit the published sitemap tree for cross-origin links
When it happens
Trigger: A <loc> entry in a sitemapindex points to a different origin, contains ?query or #fragment, embeds user:pass@ credentials, or its pathname does not end with .xml — then it is shifted off the pending queue and rejected before fetching.
Common situations: Hosting rewrites that add query strings to sitemap URLs; sitemaps generated for the www domain while the script uses the apex origin; sitemap plugin outputting .xml.gz or HTML links.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- published sitemap tree exceeds 50 documents
- ${location} returned ${response.status}, expected direct 200
- invalid sitemap document: ${location}
- invalid sitemap entries: ${location}
- expected one location per sitemap entry: ${location}
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/2ea18921bc1cd1d7.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/seo-indexnow-submit.mjs:170
export const INDEXNOW_BATCHES = Object.freeze([
batch(APEX_HOST, APEX_URLS, APEX_INDEXNOW_KEY),
batch(WWW_HOST, WWW_URLS),
// The sitemap lists each variant's canonical /dashboard; the bare root is the
// AI-crawler stub surface middleware.ts serves, so submit both.
...INDEXNOW_VARIANT_HOSTS.map((host) => batch(host, urlsForHost(host, [`https://${host}/`]))),
]);
export async function getPublishedBatches({ fetchImpl = globalThis.fetch } = {}) {
const origin = SITE_ORIGIN;
const pending = [`${origin}/sitemap.xml`];
const seen = new Set();
const pages = new Set();
const hosts = new Set(INDEXNOW_BATCHES.map(config => config.host));
while (pending.length > 0) {
const location = pending.shift();
const sitemapUrl = new URL(location);
if (sitemapUrl.origin !== origin || sitemapUrl.username || sitemapUrl.password || sitemapUrl.search || sitemapUrl.hash || !sitemapUrl.pathname.endsWith('.xml')) {
throw new Error(`invalid sitemap location: ${location}`);
}
if (seen.has(location)) continue;
seen.add(location);
if (seen.size > 50) throw new Error('published sitemap tree exceeds 50 documents');
const response = await fetchImpl(location, {
method: 'GET',
redirect: 'manual',
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
headers: { Accept: 'application/xml', 'User-Agent': USER_AGENT },
});
if (response.status !== 200) throw new Error(`${location} returned ${response.status}, expected direct 200`);
const source = (await response.text()).trim();
const root = /^(?:<\?xml[^?]*\?>\s*)?<(sitemapindex|urlset)\b[^>]*>([\s\S]*)<\/\1>\s*$/.exec(source);
if (!root) throw new Error(`invalid sitemap document: ${location}`);
const tag = root[1] === 'sitemapindex' ? 'sitemap' : 'url';
const entryPattern = new RegExp(`<!--[\\s\\S]*?-->|<${tag}>[\\s\\S]*?<\\/${tag}>`, 'g');
const entries = [...root[2].matchAll(entryPattern)].filter(([entry]) => !entry.startsWith('<!--'));
if (root[2].replace(entryPattern, '').trim()) throw new Error(`invalid sitemap entries: ${location}`);View on GitHub (pinned to 7d06c8633d)