From ffffd5f1bef25587df81a09b82e3a7be883b9858 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 16 Oct 2026 16:10:14 +0000 Subject: [PATCH 1/3] Prerender public pages, serve real 404s, retire duplicate brand pages 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 --- README.md | 2 +- docs/architecture.md | 2 +- docs/operations.md | 11 +- index.html | 78 +--- knip.json | 1 + package.json | 2 +- public/about-debatelab/index.html | 39 -- public/ai-debate-case-generator/index.html | 8 +- public/ai-debate-coach/index.html | 18 +- public/ai-debate-evidence-research/index.html | 8 +- public/ai-debate-practice/index.html | 18 +- public/ai-rebuttal-generator/index.html | 8 +- .../ai-tools-for-debate-students/index.html | 8 +- .../ai-vs-traditional-debate-prep/index.html | 8 +- public/argument-flowchart/index.html | 18 +- public/best-ai-debate-tools/index.html | 8 +- .../british-parliamentary-debate/index.html | 8 +- public/congress-debate/index.html | 8 +- .../debate-argument-flowchart-tool/index.html | 8 +- public/debate-block-file-guide/index.html | 8 +- public/debate-case-generator/index.html | 18 +- public/debate-clash-points/index.html | 8 +- public/debate-collapsing-strategy/index.html | 8 +- .../debate-cross-examination-tips/index.html | 8 +- public/debate-evidence-finder/index.html | 18 +- .../debate-evidence-verification/index.html | 8 +- public/debate-fallacies/index.html | 8 +- public/debate-impact-calculus/index.html | 8 +- public/debate-judge-adaptation/index.html | 8 +- public/debate-lab/index.html | 39 -- public/debate-motion-analysis/index.html | 8 +- public/debate-note-taking-flowing/index.html | 8 +- public/debate-prep-time-management/index.html | 8 +- .../index.html | 8 +- public/debate-rebuttal-practice/index.html | 18 +- public/debate-speech-structure/index.html | 8 +- public/debate-topics-for-students/index.html | 8 +- public/debate-weighing-guide/index.html | 8 +- public/debatelab-ai/index.html | 39 -- public/debatelab-alternatives/index.html | 8 +- public/debatelab-app/index.html | 39 -- public/debatelab-by-programcode/index.html | 39 -- public/debatelab-pricing/index.html | 8 +- public/debatelab-vs-chatgpt/index.html | 8 +- public/debatelab-vs-debateclub/index.html | 8 +- public/debatelab-vs-windebate/index.html | 8 +- public/debatelab/index.html | 39 -- .../how-to-cross-examine-in-debate/index.html | 8 +- public/how-to-flow-in-debate/index.html | 8 +- public/how-to-practice-rebuttals/index.html | 8 +- .../index.html | 8 +- public/how-to-weigh-in-debate/index.html | 8 +- public/how-to-write-a-debate-case/index.html | 18 +- .../how-to-write-a-rebuttal-speech/index.html | 8 +- public/humans.txt | 4 +- public/lincoln-douglas-debate/index.html | 8 +- public/llms.txt | 14 +- public/online-debate-timer-tool/index.html | 8 +- public/online-debate-timer/index.html | 18 +- public/policy-debate/index.html | 8 +- public/public-forum-debate/index.html | 8 +- public/sitemap.xml | 357 ------------------ public/what-is-debatelab/index.html | 39 -- public/world-schools-debate/index.html | 8 +- public/wsdc-debate-practice/index.html | 8 +- scripts/generate_seo_content.py | 48 +-- scripts/prerender.mjs | 127 +++++++ scripts/seo_hour_watch.py | 7 +- scripts/sitemap.mjs | 48 +++ src/App.tsx | 27 +- src/components/landing/Reveal.tsx | 7 +- src/components/layout/PublicPageShell.tsx | 2 +- src/i18n.ts | 1 - src/lib/route-modules.ts | 16 +- src/lib/seo/pages.ts | 45 +++ src/lib/seo/use-page-head.ts | 44 +++ src/lib/supabase/AuthProvider.tsx | 31 +- src/lib/supabase/auth-context.ts | 33 ++ src/locales/en.json | 2 +- src/locales/es.json | 2 +- src/locales/hu.json | 2 +- src/main.tsx | 29 +- src/pages/LandingPage.tsx | 2 + src/pages/NotFound.tsx | 3 + src/pages/PrivacyPage.tsx | 2 + src/pages/TermsPage.tsx | 2 + src/prerender/entry.tsx | 160 ++++++++ src/test/vercel-deployment-config.test.ts | 22 +- vercel.json | 45 ++- vite.config.ts | 40 +- 90 files changed, 881 insertions(+), 1082 deletions(-) delete mode 100644 public/about-debatelab/index.html delete mode 100644 public/debate-lab/index.html delete mode 100644 public/debatelab-ai/index.html delete mode 100644 public/debatelab-app/index.html delete mode 100644 public/debatelab-by-programcode/index.html delete mode 100644 public/debatelab/index.html delete mode 100644 public/sitemap.xml delete mode 100644 public/what-is-debatelab/index.html create mode 100644 scripts/prerender.mjs create mode 100644 scripts/sitemap.mjs create mode 100644 src/lib/seo/pages.ts create mode 100644 src/lib/seo/use-page-head.ts create mode 100644 src/lib/supabase/auth-context.ts create mode 100644 src/prerender/entry.tsx diff --git a/README.md b/README.md index 3ce2e0f8f..17a9797f2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Browser or PWA -> OpenRouter models, research providers, Stripe, Google APIs ``` -- The frontend is hosted on **Vercel**. [`vercel.json`](vercel.json) owns the build, SPA fallback, cache policy, and security headers. Merging to `main` deploys it to production. +- The frontend is hosted on **Vercel**. [`vercel.json`](vercel.json) owns the build, app-route rewrites, cache policy, and security headers. Merging to `main` deploys it to production. - **Supabase** holds auth, data, and Edge Functions. Migrations and function deploys are separate operations that need explicit authorization. - The browser never holds provider secrets. AI requests go to the `ai` and `ai-stream` Edge Functions, which check auth, quotas, and controls, load debate sources by ID, call the model through OpenRouter, validate the output, and record usage. diff --git a/docs/architecture.md b/docs/architecture.md index f70b1eca2..00bf25c0b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -144,6 +144,6 @@ The service worker and manifest provide installable web assets and an offline ap ## Deployment boundary -Vercel builds `dist/` with Node 22 and serves it. [`vercel.json`](../vercel.json) owns SPA fallback, cache policy, crawler restrictions, and response security headers. Merging to `main` deploys the frontend to production; Supabase migrations and Edge Functions deploy separately. GitHub workflow files own quality, live-provider, staging, and canary automation. +Vercel builds `dist/` with Node 22 and serves it. [`vercel.json`](../vercel.json) owns the app-route rewrites, the 404 for everything else, cache policy, crawler restrictions, and response security headers. Merging to `main` deploys the frontend to production; Supabase migrations and Edge Functions deploy separately. GitHub workflow files own quality, live-provider, staging, and canary automation. Read [operations.md](operations.md) before changing deployment behavior and [testing.md](testing.md) before claiming a boundary works. diff --git a/docs/operations.md b/docs/operations.md index 1333c11a8..9f0fe5b3c 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -41,12 +41,15 @@ Vercel builds and serves the frontend from the Git repository. Merging to `main` [`vercel.json`](../vercel.json) owns: - the Vite build command and `dist` output; -- SPA fallback to `index.html`; -- no-cache headers for `/`, `index.html`, `theme-init.js`, and `sw.js`, and immutable caching for hashed `/assets/`; +- rewrites of the app routes (`/app`, `/login`, `/share`, `/expocit/sources`) to `app-shell.html`; any other path that is not a file gets `404.html` with a real 404 status; +- 301 redirects from the retired brand pages (`/debatelab`, `/debatelab-by-programcode`, and the rest) to `/`; +- no-cache headers for `/`, `index.html`, `app-shell.html`, `theme-init.js`, and `sw.js`, and immutable caching for hashed `/assets/`; - `noindex` headers for `/app`, `/login`, and `/share` routes; - Content Security Policy and the other response security headers, including the `Permissions-Policy` that allows the microphone for the app's own origin only. -The build also writes `dist/terms/index.html` and `dist/privacy/index.html`: copies of the app shell whose canonical URL names their own route instead of the landing page (`selfCanonicalShells` in [`vite.config.ts`](../vite.config.ts)). Vercel serves them before the SPA fallback. Add a route there when a new SPA page joins `public/sitemap.xml`. +After `vite build`, [`scripts/prerender.mjs`](../scripts/prerender.mjs) renders the landing page, `/terms`, `/privacy`, and the 404 page in Node through [`src/prerender/entry.tsx`](../src/prerender/entry.tsx). It writes them to `dist/index.html`, `dist/terms/index.html`, `dist/privacy/index.html`, and `dist/404.html`, each with its own title, description, and canonical URL from [`src/lib/seo/pages.ts`](../src/lib/seo/pages.ts); the landing page also carries the Organization and SoftwareApplication JSON-LD. The built shell is kept as `dist/app-shell.html`. On load, the client replaces the prerendered HTML with the same page. A new public SPA page needs an entry in `PAGE_HEADS`, in the prerender entry, and a route kept out of the 404. + +[`scripts/sitemap.mjs`](../scripts/sitemap.mjs) then writes `dist/sitemap.xml` from every page in `dist/` that names itself as canonical and is not `noindex`. Each `` is the `dateModified` the page declares in its JSON-LD: the guide pages' `CONTENT_DATE` in [`scripts/generate_seo_content.py`](../scripts/generate_seo_content.py), the legal pages' "last updated" dates, and the landing page's last source commit. A page without a date is listed without ``. [`src/test/vercel-deployment-config.test.ts`](../src/test/vercel-deployment-config.test.ts) guards these rules. Update it together with `vercel.json`, and do not paste the full configuration into another guide. @@ -205,6 +208,6 @@ Do not expose protected data in artifacts. Record the commit, environment, opera - Green local E2E plus staging failure: schema, functions, provider access, demo code, or CORS may differ. Run the staging suite locally first; if it fails there too on a UI step, the spec is stale, not staging. - New frontend errors against Supabase right after a merge: the matching migration or function may not be applied to production yet. Run `npm run backend:status` from the linked checkout. - Function returns unauthorized after deploy: check handler auth and environment propagation before changing gateway settings. -- Browser works on one route only: check the `vercel.json` SPA rewrite and cache headers. +- Browser works on one route only, or an app route shows the 404 page: check the `vercel.json` app-route rewrites and cache headers. Read [testing.md](testing.md) for evidence rules and [`../supabase/README.md`](../supabase/README.md) for backend ownership. diff --git a/index.html b/index.html index 2b8b3d796..8ad297185 100644 --- a/index.html +++ b/index.html @@ -6,21 +6,20 @@ - DebateLab by Pi-Stack | AI Debate Prep, Cases & Rebuttals - + + DebateLab - + - - - + @@ -28,46 +27,6 @@ -