Skip to content

Fix /sitemap.xml 500s from request-time cookies() - #7

Merged
Chinmay-KB merged 1 commit into
mainfrom
cursor/fix-sitemap-500-3dcb
Aug 13, 2026
Merged

Fix /sitemap.xml 500s from request-time cookies()#7
Chinmay-KB merged 1 commit into
mainfrom
cursor/fix-sitemap-500-3dcb

Conversation

@Chinmay-KB

Copy link
Copy Markdown
Owner

TL;DR

/sitemap.xml used the cookie-based Supabase SSR client (cookies() from next/headers) inside a metadata route that is supposed to be cached (revalidate = 3600). That is a request-time API, so the sitemap could not be statically generated, getSupabasePublicEnv() / cookies() throws were uncaught 500s, and an Invalid Date lastModified would crash Next’s XML serializer (toISOString()).

This PR fetches published mashups with the same cookie-less anon client as the homepage feed, omits bad dates, and always returns the static marketing URLs so the response stays valid XML even with zero generations. robots.txt is unchanged.

Root cause

src/lib/sitemap-data.ts called createSupabaseServerClient(), which:

  1. await cookies() — Next.js 16 treats this as a request-time API. Installed docs: sitemap.js is a special Route Handler that is cached by default unless it uses a Request-time API.”
  2. getSupabasePublicEnv() throws if public Supabase env is missing (homepage already uses tryGetSupabasePublicEnv()).
  3. No try/catch around client creation — a throw 500s the whole route instead of serving static URLs.
  4. new Date(row.updated_at) as lastModified — Next serializes dates with date.toISOString(). An invalid date throws RangeError and 500s the entire sitemap.

Live probe from this agent (before the fix, production main):

  • curl https://xbuildsy.com/sitemap.xml often returned 200 with valid XML (41 URLs: 4 static + 33 /g/… + 4 /u/…).
  • Every 200 still had x-vercel-cache: MISS — the sitemap was not ISR-cached, consistent with cookies() forcing dynamic rendering.
  • A fetch-as-markdown client still saw 500, so the failure is at least intermittent / client-path dependent rather than “empty generations.” Empty generations already mapped to []; the 500 is an uncaught throw, not an empty list.

Local yarn build on this branch now reports:

○ /robots.txt
○ /sitemap.xml                                                  1h      1y

i.e. the sitemap is prerendered with the intended 1h revalidate instead of ƒ dynamic.

What changed

  • src/lib/sitemap-data.ts — one public generations query via createPublicFeedClient() + publicGenerationsQuery (same filters as the feed). Failures return { generations: [], creators: [] }. Invalid/blank slugs, creator ids, and dates are skipped. lastModified is an ISO string or omitted.
  • src/app/sitemap.ts — thin wrapper with a static-URL fallback if anything still throws.
  • src/lib/__tests__/sitemap-data.test.ts — empty catalog, invalid dates, missing client, thrown query.
  • Not changed: src/app/robots.ts (still points at ${base}/sitemap.xml).

How to review

  1. src/lib/sitemap-data.ts — confirm we no longer import @/lib/supabase/server.
  2. src/app/sitemap.ts — confirm static fallback.
  3. Tests in src/lib/__tests__/sitemap-data.test.ts.

Verification

  • yarn test — 88 passed (including 10 new sitemap tests)
  • yarn lint — 0 errors (pre-existing warnings only)
  • yarn build (CI placeholder Supabase env) — compiled; /sitemap.xml is static with 1h revalidate; /robots.txt still static

Post-merge live checks

Production still serves the old dynamic sitemap until this lands on main. After deploy:

  1. curl -sI https://xbuildsy.com/sitemap.xml — expect 200, content-type: application/xml, and ideally x-vercel-cache: HIT (or STALE) on a second request rather than perpetual MISS.
  2. Body is well-formed XML and still includes /, /feed, /generate, /about plus /g/{slug} and /u/{id} when mashups exist.
  3. https://xbuildsy.com/robots.txt still has Sitemap: https://xbuildsy.com/sitemap.xml.

Other site-health findings (not fixed here)

  • Site-health CI does not probe live URLs. .github/workflows/site-health.yml only runs yarn build / test / lint with placeholder env. It would not have caught a production /sitemap.xml 500.
  • /images is 404. An older SEO commit advertised /images; the page was later removed. Not in the current sitemap.
  • Cloudflare prepends managed robots.txt rules (bot Disallows + Content-Signal) above the Next.js robots.ts output. The Next Sitemap: line is still present. Out of scope (no DNS/domain edits).
  • GET /u/not-a-user returns 200 (soft missing profile) rather than 404 — crawlers may index empty profile URLs listed in the sitemap. Separate issue.
  • Vercel project if-x-built-y.vercel.app/sitemap.xml also 200’d with the same body as the apex domain when probed from this VM.
Open in Web Open in Cursor 

Stop using the cookie-based Supabase server client in the cached
sitemap route. Fetch published mashups with the public anon client,
omit invalid dates, and always fall back to static URLs so the
response stays valid XML even with zero generations.

Co-authored-by: Chinmay Kabi <chinmay@blend.to>
@Chinmay-KB
Chinmay-KB marked this pull request as ready for review August 13, 2026 13:37
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
if-x-built-y Ready Ready Preview Aug 13, 2026 1:39pm

@supabase

supabase Bot commented Aug 13, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project bjsepxonrrnwwxaejkrv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@Chinmay-KB
Chinmay-KB merged commit 345d8e4 into main Aug 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants