01
One file: app/sitemap.ts
A default-exported function returning MetadataRoute.Sitemap, served automatically at /sitemap.xml. Make it async if it needs to pull blog slugs from the database, because it runs at build time by default. Only url is required per entry. Set lastModified when it is real, and skip changeFrequency and priority entirely, because Google ignores both.
02
Public pages only. This is the hard rule.
List only what a logged-out stranger should see: landing, pricing, blog, legal. Never /dashboard, /admin, /app, /login, /api. The sitemap is a public file, so listing private routes publishes a map of your app's internal surface. Googlebot also crawls what you list, so login redirects become junk results under your brand. If a “protected” page only checks auth client-side, a common bug in AI-generated code, the sitemap fast-tracks it straight into the index. If something private does get indexed, the Removals tool hides it from results within about a day for six months, which buys the time to fix the actual auth.
03
Pair it with app/robots.ts
Same convention, served at /robots.txt. It has two jobs. Point crawlers at the sitemap with its absolute URL, and keep them off private paths. Never Disallow a path that appears in the sitemap, because those are contradictory signals. And remember robots.txt is a request, not security. A blocked URL can still appear in results as a bare title if anything links to it. Pages that must stay out of Google need noindex. Pages that must stay private need server-side auth.
04
Verify the domain in Search Console
Add a Domain property, the left option. One verification covers www and every subdomain. Google hands you a TXT record, and it goes wherever your DNS actually lives, which for most setups is the registrar. Only add it in Vercel if your domain genuinely uses Vercel's nameservers. Connecting a domain with A and CNAME records leaves DNS at the registrar, and a TXT added in the wrong place verifies nothing. Propagation is usually minutes, and the dialog lets you retry without losing anything.
05
Submit, then actually wait
Sitemaps → paste sitemap.xml → Submit. A “Couldn't fetch” shown immediately after submission usually means Google has not fetched it yet, so wait a day before debugging. Genuine failures are 404s, server errors or robots blocks, and a malformed file shows a parse error instead. Success means the XML parsed and the URLs are queued. It does not mean indexed. A brand-new domain takes one to three weeks to index meaningfully, and “Discovered, currently not indexed” is normal early. A sitemap earns zero rankings. It just guarantees Google knows the pages exist.
The thirty-second test
Open /sitemap.xml logged out: valid XML, and every URL is a page you would happily show a stranger. Search Console shows the sitemap row as Success with a count near what you listed. Both true means done. Check Indexing → Pages in two weeks, not tomorrow.