DEV Community

AI Dev Hub
AI Dev Hub

Posted on

A valid sitemap.xml for 1,247 URLs with a free generator in 2026

A valid sitemap.xml for 1,247 URLs with a free generator in 2026

Paste your URLs into a client-side sitemap generator, add lastmod, and ignore changefreq and priority. Google ignores both, and has for years. A valid sitemap.xml is nine lines of boilerplate plus one url block per page, capped at 50,000 URLs or 50MB uncompressed. The XML is the easy part. Keeping the list free of redirects and 404s is what actually moves crawl budget.

Up front: the sitemap generator I link to below is one I built. I tried six existing ones in March 2026 and every one either uploaded my URL list to a server or capped the free tier at 100 URLs. Mine is free, runs entirely in the browser, no signup, and nothing leaves your machine. If you know a better one, tell me and I'll link it instead.

The migration that shipped 412 dead URLs

In February 2026 I moved a docs site off a flat /guides/ structure and onto /docs/<version>/. Around 1,247 pages. The sitemap came from a shell pipeline I wrote back in 2023: find the build output and wrap each line in <loc> tags with sed. It had worked for three years without a single complaint.

It kept working after the migration. That was the problem.

find walked the old dist/guides/ directory, which the new build never cleaned out. So the sitemap listed 412 URLs that returned 404 sitting next to the 1,247 real ones. Search Console flagged it 9 days later under "Submitted URL not found". By then Googlebot had burned a chunk of its crawl budget on pages that didn't exist, and half the new /docs/ tree was still unindexed.

I fixed it the dumb way first: rm -rf dist/ at the top of the build. That killed the stale files and immediately created a different problem. My sed pipeline had no concept of lastmod, so every entry either got an identical timestamp or none at all, depending on which branch of the script ran. A sitemap claiming all 1,247 pages changed at the same second tells a crawler nothing. I was wrong to treat lastmod as optional decoration. It's the one optional field Google actually reads.

Attempt two was a Node script using the sitemap npm package. That's still what runs in CI today and I have no complaints about it. It's the wrong tool when a colleague drops a CSV of 90 URLs in Slack and wants a sitemap before lunch. Installing a dependency and re-reading the API docs for a one-off costs 20 minutes I'd rather not spend.

That's the gap a paste-in generator fills. Paste, configure, download. 47 seconds, nothing to maintain afterward.

What a valid sitemap.xml actually needs

The spec is smaller than most people expect. sitemaps.org froze at version 0.9 and never moved. You need one urlset element carrying the namespace, and inside it one url element per page with a required loc. Everything else is optional.

The rules that actually bite:

  • loc has to be absolute and fully qualified, under 2,048 characters. Relative paths are invalid, and plenty of generators emit them anyway.
  • Ampersands and angle brackets inside URLs must be entity-escaped. ?a=1&b=2 becomes ?a=1&amp;b=2. This is the most common reason a hand-rolled sitemap fails validation.
  • lastmod must be W3C Datetime. 2026-08-10 is legal. 2026-08-10T14:32:00+00:00 is legal. 08/10/2026 gets the file rejected.
  • 50,000 URLs or 50MB uncompressed per file, whichever hits first. Past that you need a sitemap index pointing at multiple files.
  • Every URL has to share a host with the sitemap's own location, unless you've verified cross-domain ownership in Search Console.

changefreq and priority are still valid elements, and Google ignores both. Bing too. I still emit priority out of habit, which is probably pointless and definitely harmless.

Here's the whole thing as a script, which is what every sitemap generator does underneath, browser-based ones included:

// build-sitemap.mjs  ->  node build-sitemap.mjs urls.txt > sitemap.xml
import { readFileSync } from "node:fs";

const esc = (s) =>
  s.replace(/&/g, "&amp;")
   .replace(/</g, "&lt;")
   .replace(/>/g, "&gt;")
   .replace(/"/g, "&quot;")
   .replace(/'/g, "&apos;");

const urls = readFileSync(process.argv[2], "utf8")
  .split("\n")
  .map((l) => l.trim())
  .filter(Boolean);

if (urls.length > 50000) {
  throw new Error(`${urls.length} URLs: split these into a sitemap index`);
}

const today = new Date().toISOString().slice(0, 10);

const body = urls
  .map((u) => `  <url>\n    <loc>${esc(u)}</loc>\n    <lastmod>${today}</lastmod>\n  </url>`)
  .join("\n");

process.stdout.write(
  `<?xml version="1.0" encoding="UTF-8"?>\n` +
  `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${body}\n</urlset>\n`
);

// $ printf 'https://example.com/\nhttps://example.com/s?a=1&b=2\n' > urls.txt
// $ node build-sitemap.mjs urls.txt
// <?xml version="1.0" encoding="UTF-8"?>
// <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
//   <url>
//     <loc>https://example.com/</loc>
//     <lastmod>2026-08-10</lastmod>
//   </url>
//   <url>
//     <loc>https://example.com/s?a=1&amp;b=2</loc>
//     <lastmod>2026-08-10</lastmod>
//   </url>
// </urlset>
Enter fullscreen mode Exit fullscreen mode

That's 25 lines and it covers escaping plus the 50k guard. The browser version I built adds per-URL lastmod editing and a validation pass before download, which is the piece I kept missing in the script. The sitemap generator on aidevhub parses its own output back before handing you the file, so a malformed URL fails in the tab instead of in Search Console 9 days later. It all runs client-side, so the list never leaves the browser.

How it compares to the alternatives

I checked four options in March 2026 before deciding to build anything. Here's how they line up.

aidevhub generator sitemap npm package XML-Sitemaps.com Screaming Frog
Cost Free Free Free to 500 pages, ~$20/yr after Free to 500 URLs, GBP 199/yr after
Where your URLs go Stays in the browser Stays local Uploaded to their server Stays local
Setup time None npm install plus a script None 250MB desktop install
Finds URLs for you No, you paste them No Yes, it crawls Yes, full crawl
Best fit One-off lists up to 50k CI pipelines Small sites, no dev on hand Audits and large sites

The real split is URL discovery. Screaming Frog and XML-Sitemaps crawl your site and find the pages for you, which matters a lot when nobody has an authoritative list. That crawl is also the expensive part, and it's exactly where both hit you with limits: 500 URLs free, money after.

Paste-in tools skip discovery and assume you already have the list. If you run any static site generator, you do. My build already knows every route it emitted. Making a crawler rediscover them is work I've done once already.

The privacy column matters more than people admit. A URL list from staging or an internal tool leaks structure: admin paths and unreleased feature routes. Uploading that to a third-party server to get 40 lines of XML back is a trade I stopped making after the second time a security review asked me about it.

When you shouldn't use a paste-in generator

The list of bad fits is longer than the pitch usually admits.

  • You don't have a URL list. The tool takes URLs. It doesn't crawl. If you've inherited a WordPress install with an unknown page count, run a crawler first and paste the output in second.
  • The sitemap has to regenerate on every deploy. Automate it instead. The script above is 25 lines and costs nothing to run in CI.
  • You're past 50,000 URLs. Now you need a sitemap index plus per-shard files, and that's a build-time job.
  • You need the image or video extensions. Those pull in extra namespaces that simple generators don't emit.
  • Your CMS already ships one. WordPress with Yoast, or Next.js with its sitemap route, already handles this. Hand-generating on top of that guarantees it goes stale the first time someone publishes.

Honestly, the case for a manual generator is narrower than I'd like: one-off lists and migrations, plus sites without a build step that emits XML. It just happens to be a case that lands on me 4 or 5 times a year and annoys me every single time.

FAQ

Q: Will a sitemap improve my rankings?
A: No. A sitemap affects discovery, so it helps a crawler find pages it might otherwise miss (deep pages, weak internal linking, fresh content). Ranking is a separate question entirely. If your pages are already indexed, adding a sitemap changes nothing.

Q: Do I still need changefreq and priority in 2026?
A: No. Google has said publicly for years that it ignores both, and Bing treats them the same way. They're valid XML, so including them won't break anything. I leave priority in because muscle memory is hard to unlearn.

Q: How do I actually submit the file?
A: Drop it at https://yoursite.com/sitemap.xml, add Sitemap: https://yoursite.com/sitemap.xml to your robots.txt, then submit the URL in Search Console under Sitemaps. The robots.txt line is what other crawlers use, so don't skip it.

Q: Is a browser-based generator safe for internal URLs?
A: Depends on the tool. Client-side ones do the string building in JavaScript in your tab, so nothing is transmitted. Open devtools, switch to the Network panel, and generate a file. If you see zero requests, it's local. That check takes 10 seconds and I'd run it on any tool before pasting a staging URL list.

Written with AI assistance and human review. Try the tool at aidevhub.io/sitemap-generator.

Top comments (0)