From e71a2d43f4c2e5240f7862bc3cee2d5720669460 Mon Sep 17 00:00:00 2001 From: okcodes <80954089+okcodes@users.noreply.github.com> Date: Sun, 6 Sep 2026 06:42:30 -0600 Subject: [PATCH 1/2] website: prerender to static HTML so no-JS clients get real content Renders the app to a string at build time via a separate SSR entry and inlines it into dist/index.html's #root, instead of shipping an empty div for client-side React to fill in. Lets crawlers and AI agents that don't execute JS see the actual page content. Chose this over migrating to Next.js since the site deploys to GitHub Pages (static hosting only), where Next's SSR/ISR advantages aren't available anyway. See DECISIONS.md. Co-Authored-By: Claude Sonnet 5 --- DECISIONS.md | 22 ++++++++++++++++++++++ website/package.json | 4 ++-- website/scripts/prerender.mjs | 24 ++++++++++++++++++++++++ website/src/entry-server.tsx | 6 ++++++ website/src/main.tsx | 13 ++++++++++++- website/vite.ssr.config.ts | 12 ++++++++++++ 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 website/scripts/prerender.mjs create mode 100644 website/src/entry-server.tsx create mode 100644 website/vite.ssr.config.ts diff --git a/DECISIONS.md b/DECISIONS.md index bffeca9..58b7f67 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -160,6 +160,28 @@ for this reason (2026-08-26), which also required an org-level `members_can_crea to be enabled - a broader-scoped, org-wide change that was called out and confirmed separately before touching it, since it wasn't implied by "make this one repo public." +**The website prerenders to static HTML at build time, not just client-side React.** Before this, +`dist/index.html` shipped an empty `
` - fine for a browser, but a no-JS crawler +(and some AI agents fetching the page) got nothing. Next.js was considered instead of fixing this +in Vite, since SSR is its headline feature - but `release-website.yml` deploys to GitHub Pages, +which only serves static files with no Node process behind it. Next's SSR/ISR/server-actions +advantages need a server to run on, so on Pages it would run in `output: 'export'` (fully static) +mode anyway - the same static-HTML output Vite already produces, just with the App Router's +routing/data-fetching machinery for a site that has exactly one page and no `react-router`. That +would have been a full framework migration for a capability Vite can add directly. + +The fix: `vite.ssr.config.ts` builds `src/entry-server.tsx` (a `renderToString()` wrapper) to +`dist-ssr/`, and `scripts/prerender.mjs` inlines that HTML into `dist/index.html`'s `#root`, then +deletes `dist-ssr/` - it's a build-time intermediate, not a deploy artifact. `src/main.tsx` now +hydrates when `#root` already has children (a real build/preview load) and falls back to a normal +`createRoot().render()` when it's empty (`vite dev`, which serves `index.html` unprerendered). +`vite.ssr.config.ts` only carries the `react()` plugin, not `virtualConsoleVitePlugin` or +`tailwindcss()` - the console plugin unconditionally emits its own client chunk on `command === +'build'`, which breaks when pointed at a non-HTML SSR entry, and neither plugin's output (injected +`