From 5e8e551e2c05440bf4624500609fe3c6061a8eac Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Wed, 16 Sep 2026 16:54:42 +0800 Subject: [PATCH] fix(www): include app routes in sitemap (#50277) I added static App Router pages to the www sitemap, including the homepage, pricing, and product pages. The generator previously scanned only Pages Router and content files; it now strips route groups, excludes dynamic segments, and emits these URLs without lastmod. **Note:** The pre-existing Pages Router `/opt-out/[ref]` entry remains outside this change. ## To test Tested on the [www preview](https://zone-www-dot-com-git-pamela-growth-1214-app-rou-1d4879-supabase.vercel.app/sitemap_www.xml): - [x] Open `/sitemap_www.xml`: expect the homepage, `/pricing`, and product routes once each, without route-group names or lastmod on those entries. - [x] Compare the sitemap's changelog URLs with `/changelog-rss.xml`: expect every RSS item link to remain included, including text-slug entries. - [x] Open `/sitemap.xml`: expect the existing www and docs sitemap links. ## Linear - fixes GROWTH-1214 ## Summary by CodeRabbit - **New Features** - Sitemap generation now includes static pages built with the Next.js App Router. - Route groups are correctly omitted from generated URLs. - Dynamic App Router routes are excluded from the sitemap. - **Bug Fixes** - Improved sitemap coverage and URL accuracy for applications using both App Router and Pages Router pages. --- apps/www/generate-sitemap.test.ts | 40 +++++++++++++++++++++++++ apps/www/internals/generate-sitemap.mjs | 8 +++++ 2 files changed, 48 insertions(+) diff --git a/apps/www/generate-sitemap.test.ts b/apps/www/generate-sitemap.test.ts index 7778886cf17..42be8704b6d 100644 --- a/apps/www/generate-sitemap.test.ts +++ b/apps/www/generate-sitemap.test.ts @@ -189,6 +189,46 @@ describe('generate-sitemap lastmod', () => { }) }) +describe('generate-sitemap App Router pages', () => { + it.each(['app/page.tsx', 'app/(home)/page.tsx'])( + 'includes only static page URLs without lastmod, with homepage at %s', + (homepage) => { + const dir = writeFixture({ + [homepage]: '', + 'app/(group)/example/page.tsx': '', + 'app/(marketing)/guides/(topics)/start/page.tsx': '', + 'app/blog/example/page.tsx': '', + 'app/events/example/page.tsx': '', + 'app/reference/index/page.tsx': '', + 'app/things/[slug]/page.tsx': '', + 'app/things/[slug]/details/page.tsx': '', + 'app/(group)/[...slug]/page.tsx': '', + 'app/optional/[[...slug]]/page.tsx': '', + 'app/example/layout.tsx': '', + 'app/api/route.ts': '', + 'app/loading.tsx': '', + 'pages/company.tsx': '', + }) + const result = runGenerator(dir) + expect(result.status, result.stderr).toBe(0) + const entries = urlEntries(fs.readFileSync(path.join(dir, 'public/sitemap_www.xml'), 'utf-8')) + expect(entries.sort((a, b) => a.loc.localeCompare(b.loc))).toEqual( + [ + '', + '/blog/example', + '/company', + '/evals', + '/events/example', + '/example', + '/guides/start', + '/reference/index', + ].map((route) => ({ loc: `https://supabase.com${route}`, lastmod: undefined })) + ) + }, + SPAWN_TIMEOUT_MS + ) +}) + describe('frontmatter dates in sitemap and blog JSON-LD', () => { it.each([ ['2026-01-14T09:30:00', '2026-01-14'], diff --git a/apps/www/internals/generate-sitemap.mjs b/apps/www/internals/generate-sitemap.mjs index 2a879479f1c..0f64bfca847 100644 --- a/apps/www/internals/generate-sitemap.mjs +++ b/apps/www/internals/generate-sitemap.mjs @@ -65,6 +65,7 @@ async function generate() { 'pages/*.tsx', 'pages/*.mdx', 'pages/**/*.tsx', + 'app/**/page.tsx', '_blog/*.mdx', '_case-studies/*.mdx', '_customers/*.mdx', @@ -90,6 +91,13 @@ async function generate() { // Generate URLs for static pages const staticUrls = pages .map((page) => { + if (page.startsWith('app/')) { + const segments = page.split('/').slice(1, -1) + if (segments.some((segment) => segment.includes('['))) return null + const path = segments.filter((segment) => !/^\(.*\)$/.test(segment)).join('/') + return urlEntry(`https://supabase.com${path ? `/${path}` : ''}`) + } + const path = page .replace('.next/server/pages', '') .replace(/^pages/, '')