Appearance
Prerender the homepage (/) — implementation plan
Branch: feat/prerender-home. Worktree: /Users/jeffjassky/Projects/scenedetect-prerender-home. Run tests with yarn test from the worktree root (node_modules is symlinked).
Why
/ is served as the bare SPA shell (<div id="app"></div>) plus server-injected JSON-LD. Every non-JS crawler (Bing, PerplexityBot, GPTBot, ClaudeBot) sees no homepage copy at all. The other 14 marketing routes are already snapshotted at Docker-build time by scripts/prerender-marketing.mjs; this plan extends that to / generically — no per-route configuration anywhere.
Current blockers (all verified)
isReady()inscripts/prerender-marketing.mjsrequires the captured<title>to differ from the shell's.public/index.html's title is exactly the homepage's title, so/can never pass. It also requires a<script data-managed="pageHead">, whichPageHome.vuedeliberately does not declare.snapshotRoute()writes todist/<route>/index.html; for/that isdist/index.html, i.e. it would overwrite the SPA shell every other route depends on.server/middlewares/homepage-meta-inject.jsanswersGET /before the generic prerendered-file handler inserver/main.jsand always sends the bare shell.injectIntoHead()in that middleware strips everyld+jsonscript, which would delete any schema a prerendered page carried.- Prerendered pages capture
Reveal.vue'ssf-reveal--armed(CSS-hidden) state, so most sections ship CSS-hidden to non-JS crawlers.Reveal.vuehides nothing underprefers-reduced-motion.
Work packages
WP-A — prerender script + ready signal + router flag
Files: src/components/mixins/pageHeadMixin.js, scripts/prerender-marketing.mjs, server/router/spa-routes.js, server/router/sitemap-routes.test.js, src/router.js, new scripts/prerender-marketing.test.js.
WP-B — homepage middleware serves the snapshot
Files: server/middlewares/homepage-meta-inject.js, server/middlewares/homepage-meta-inject.test.js.
WP-A and WP-B touch disjoint files and can land in parallel. They share one contract:
Contract:
server/router/spa-routes.jsexportsprerenderOutDir(route)returning the directory name (relative todist/) that holdsindex.htmlfor a prerendered route.prerenderOutDir('/') === 'home'; every other route returnsroutewithout its leading slash (e.g.'/use-cases/footage-library'→'use-cases/footage-library'). WP-A implements it. WP-B must consume it (not hardcode'home'). If WP-B's tests run before WP-A lands, WP-B stubs it in the test only.