Repository navigation
SEO: prerender public pages, real 404s, 301 brand pages, built sitemap - #146
Conversation
The landing page, /terms and /privacy now ship as real HTML with their own title, description, canonical URL and JSON-LD. scripts/prerender.mjs renders them in Node through Vite after the build, plus a 404 page with noindex and no canonical. The client keeps the prerendered page on screen until its module loads, then replaces it with the same page in one render. vercel.json rewrites only app routes to app-shell.html, so any other path gets 404.html with a 404 status. The seven brand pages (/debatelab, /debatelab-by-programcode and the rest) redirect 301 to /, and the guide pages, llms.txt and footer say "DebateLab by Pi-Stack". sitemap.xml is now built from dist/: every self-canonical, indexable page, with the dateModified each page declares as its lastmod. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reachedEnable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Next included review available in 49 minutes. View limit details
ℹ️ Recent review info
📝 Walkthrough
Note 🎁 Summarized by CodeRabbit Free
Comment |
|
GitHub Actions did not run on ffffd5f. Every job stopped in 2-3 s with "The job was not started because recent account payments have failed or your spending limit needs to be increased" (run 38930216979). That is account billing, not this code. In its place,
This is a local pass, not a CI pass. |
On the Vercel preview, /debatelab/ fell through to the 404 page: Vercel matched the redirect source without its optional trailing segment. The bare, trailing-slash and subpath forms now each have their own source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Checked the
Preview deployments add |
Review found that /app/settings/, /login/ and /share/abc/ now 404: the old catch-all rewrite absorbed them, and the new app-route rewrites match exactly. trailingSlash: false redirects them to the slashless path first. The JSON-LD and og:url the build writes describe only the page first loaded, so usePageHead removes them when that page unmounts during client navigation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codex review, round 1 (ffffd5f), two findings:
|
|
Codex review, round 2 (095f5f0): the round 1 fixes hold, and there is one new finding.
|



Fixes the SEO problems from the audit: thin JavaScript-only public pages, soft 404s, seven near-duplicate brand pages, a hand-edited sitemap stamped with one date, and structured data copied onto every route.
What changed
Prerendered public pages. After
vite build,scripts/prerender.mjsloadssrc/prerender/entry.tsxthrough Vite's SSR loader and renders/,/terms,/privacyand the 404 page in Node, in English and signed out. Each gets real body HTML plus its own<title>, description, canonical URL andog:*/twitter:*tags fromsrc/lib/seo/pages.ts. The landing page now reads the same with JavaScript off. I used neither vite-react-ssg (it would replace the router) nor a Playwright pass (the Vercel build would need Chromium and its system libraries). The only app change Node needed was movingAuthContext/useAuthintoauth-context.ts, so public pages no longer import the Supabase client.On load,
mountAppkeeps the prerendered page on screen until that page's chunk has loaded, then replaces it in oneflushSyncrender. That avoids both a skeleton flash and hydration-mismatch errors for Spanish and Hungarian visitors. On that first render the landingRevealskips its entrance animation, so prerendered content never fades out and back in. Later visits animate as before.usePageHeadkeeps the title, description, canonical URL and robots tag right during client navigation, andapplyAppLanguageno longer overwrites the title.Real 404s.
vercel.jsonrewrites only/app,/app/*,/login,/share/*and/expocit/sourcestoapp-shell.html, the untouched built shell. Every other unknown path gets404.htmlwith a 404 status,noindexand no canonical. Rendered client-side,NotFounddoes the same.Brand pages.
/debatelab,/debate-lab,/debatelab-ai,/debatelab-app,/what-is-debatelab,/debatelab-by-programcodeand/about-debatelabnow 301 to/, and their files are gone. The guide generator (scripts/generate_seo_content.py) no longer emits them. It now calls the brand "DebateLab by Pi-Stack" in the nav, footer,og:site_nameand JSON-LD publisher, and I regenerated the 49 guide pages from it.llms.txt,humans.txtand the footer copyright say the same.Sitemap.
scripts/sitemap.mjsbuildsdist/sitemap.xmlfromdist/. It lists every page that names itself as canonical and isn'tnoindex, which comes to 52 URLs. Each<lastmod>is thedateModifiedthe page declares in its own JSON-LD:CONTENT_DATE(2026-07-06), which the generator no longer replaces with today's date;In a shallow clone where that date can't be known, the landing page goes into the sitemap without
<lastmod>rather than with an invented date.public/sitemap.xmlis deleted.JSON-LD on
/only. The landing page carries Organization (Pi-Stack), WebSite, SoftwareApplication (price from the locale file,operatingSystem: Web) and WebPage nodes. The app shell carries none. I dropped the oldSearchAction, because/ai-debate-practice?q=is not a search endpoint.Verification
npm run verifypassed locally at this head: docs, lint/knip, typecheck, unit tests (282), collaboration (30), edge (334), build, the service-worker offline test, and Playwright smoke (30 passed).dist/with Vercel's file-first routing and drove it in Chromium:/,/termsand/privacyreturn 200 with their own titles and canonicals;/nopereturns 404 withnoindexand no canonical;/appand/app/settingsget the shell;/debatelaband/debatelab-by-programcode/return 301 to/./to/termsto/loginupdates the title and canonical./matches the hydrated page.The audit file named in the task (
/workspace/debatelab-seo-audit.md) was not on the VPS. This PR follows the five fixes listed in the task.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Updates