Make the site fully prerendered so a plain curl returns complete content
for every route (crawlers / AI screening tools see real text, not an empty
#root), while humans keep full client interactivity.
Prerender:
- Build-time per-route render: prefetch data, renderToString, inline the
dehydrated react-query cache as window.__RQ_STATE__; client hydrateRoots
and refetches live (activity stays fresh; crawlers get the baked snapshot).
- New entry-server.tsx + prerender/{prefetch,routes,meta}.ts + run-prerender.mjs;
shared lib/ranges.ts keeps SSR and client query keys identical.
- pnpm build now: tsc -b -> vite client build -> ssr build -> prerender.
- API base absolute at build (VITE_API_BASE), relative /api/v1 in the browser.
- CSS imports moved to the client entry so the tree imports under Node.
- schema.org Person + Occupation JSON-LD and per-route title/description/og.
- UTC + explicit field widths on shared date formatting so SSR and client
hydration match byte-for-byte (fixes hydration mismatch on /activity).
- Strip non-text gist content from the CV fetch (1MB -> 25KB gzipped page).
Deploy (Gitea Actions, replaces script/deploy.sh):
- deploy.yml: on push to main, lint/test gate, build api+worker as static
musl binaries (pure-rustls, no glibc skew) + prerendered web, deploy each
over SSH as gitea_ci with scoped sudo.
- refresh.yml: daily cron re-bakes only the web snapshot so gist/activity
edits propagate without a push or bouncing the api/worker.
- script/infra-setup.sh + asset/sudoers.d/{api,worker,web}-host.conf for
one-time per-host provisioning. Secrets: RSYNC_SSH_KEY, QUERY_GITHUB_TOKEN,
QUERY_GITEA_TOKEN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7zF7Kf4JqDwa6M8Qgge9M
38 lines
1.1 KiB
JSON
38 lines
1.1 KiB
JSON
{
|
|
"name": "moments-ui",
|
|
"private": true,
|
|
"version": "0.1.0",
|
|
"type": "module",
|
|
"scripts": {
|
|
"dev": "vite",
|
|
"build": "tsc -b && VITE_API_BASE= vite build && pnpm run prerender",
|
|
"prerender": "VITE_API_BASE=\"${VITE_API_BASE:-https://rob.tn/api/v1}\" vite build --ssr src/entry-server.tsx --outDir dist-server --emptyOutDir && node run-prerender.mjs",
|
|
"preview": "vite preview",
|
|
"lint": "tsc --noEmit"
|
|
},
|
|
"dependencies": {
|
|
"@tanstack/react-query": "^5.62.0",
|
|
"bootstrap": "^5.3.3",
|
|
"rc-slider": "^11.1.7",
|
|
"react": "^19.0.0",
|
|
"react-bootstrap": "^2.10.6",
|
|
"react-bootstrap-icons": "^1.11.4",
|
|
"react-dom": "^19.0.0",
|
|
"react-markdown": "^9.0.1",
|
|
"react-router-dom": "^7.14.2",
|
|
"react-vertical-timeline-component": "^3.6.0",
|
|
"rehype-raw": "^7.0.0",
|
|
"rehype-sanitize": "^6.0.0",
|
|
"remark-gfm": "^4.0.1"
|
|
},
|
|
"devDependencies": {
|
|
"@types/node": "^25.9.3",
|
|
"@types/react": "^19.0.0",
|
|
"@types/react-dom": "^19.0.0",
|
|
"@types/react-vertical-timeline-component": "^3.3.6",
|
|
"@vitejs/plugin-react-swc": "^3.7.2",
|
|
"typescript": "~5.7.0",
|
|
"vite": "^6.0.0"
|
|
}
|
|
}
|