The cleanest approach depends on whether your sitemap is static or dynamic. For a static sitemap that rarely changes, drop sitemap.xml directly into your /public folder. Next.js serves everything in /public at the root level, so /public/sitemap.xml becomes accessible at yoursite.com/sitemap.xml with zero configuration. For dynamic sitemaps that pull from a CMS, database, or file system, use Next.js API routes or the built-in sitemap convention. In the App Router (Next.js 13+), create /app/sitemap.js or sitemap.ts and export a default function that returns an array of URL objects with loc, lastmod, changefreq, and priority fields. Next.js automatically serves this at /sitemap.xml. In the Pages Router, create /pages/api/sitemap.xml.js, set res.setHeader for Content-Type text/xml, and return your generated XML string. At Ottawa SEO, we almost always generate sitemaps dynamically because our 500+ domain portfolio updates frequently. We pull page lists from our CMS or static markdown, filter out noindex pages, set lastmod to the actual file modification date, and prioritize cornerstone content at 1.0 while FAQ pages sit at 0.6. This keeps Google's crawl budget focused where it matters. Common mistakes to avoid: - Forgetting to declare xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" in your XML root if you're hand-writing it - Hardcoding localhost URLs instead of reading from process.env.NEXT_PUBLIC_SITE_URL - Including URLs that return 404, 301, or have noindex meta tags - Skipping lastmod entirely or setting it to the current date every build, which wastes crawl budget Once your sitemap is live, submit it in Google Search Console under Sitemaps and monitor the Discovered vs. Indexed ratio. If you're running a large site with thousands of URLs, consider splitting into multiple sitemaps and using a sitemap index file to stay under the 50MB or 50,000 URL limits per file.