SEO Crawlability Playbook

A pure client-side-rendered (CSR) React + Vite SPA serves an empty<div id="root"></div>shell for every URL. All content and per-route meta tags are injected by JavaScript after load. Google can render JS in a deferred second wave, but non-JS crawlers (Bing, and social/link-preview bots like WhatsApp, LinkedIn, Facebook, X and Slack) see only the generic shell, so every shared link shows the same title, description and image. This playbook diagnoses the problem and fixes it with build-time prerendering.

Paste into your AI chat

Copy the entire playbook as markdown to use as context in Cursor, Claude, ChatGPT, or any AI tool.

The empty-shell problem

Why a client-only render hurts indexing and link previews.

!

Slower, less reliable indexing

Failed JS renders get indexed as the generic shell.

!

Duplicate titles/snippets

Every page can end up with the same title and description.

!

Non-JS crawlers see nothing

Bing is weak; link-preview bots run no JavaScript at all.

!

Next.js is usually fine

App Router SSR/SSGs by default; run the hygiene check instead.

Phase 0: Diagnose

Run these curl checks (~2 minutes) to confirm definitively whether the SPA serves an empty shell across routes.

Phase 1A: Vite build-time prerendering

After vite build, render every public route to dist/<route>/index.html with real content and correct head tags baked in. Replicate this proven reference implementation. Do not invent a new mechanism.

Env-var gotcha (Vercel / Supabase)

The build passes locally but fails on Vercel during prerender with something like "Supabase URL is not defined" thrown from a client/config module. Expect this on any Vite + Supabase site.

Phase 1B: Next.js hygiene check

Next.js App Router SSR/SSGs by default, so the empty-shell problem normally does NOT exist. This is a hygiene check, not a rebuild. Verify and fix only what is missing.

Phase 2: Shared SEO hygiene (both stacks)

Housekeeping that applies whether you prerendered a Vite SPA or verified a Next.js app.

Phase 3: Verify (evidence before claiming done)

Prove the fix worked. Evidence before claiming done.

Reference file layout

Where each concern lives in the Vite prerendering setup.

ConcernFile
Post-build rendererscripts/prerender.mjs
Route manifest (source of truth)src/prerender/routes.tsx + src/prerender/staticRoutes.json
Render + template injectionsrc/prerender/render.tsx
Sitemap generatorscripts/generate-sitemap.js
Env-aware robots.txt copyscripts/copy-robots.js
Build wiringpackage.json → "build" script
Testssrc/prerender/{routes,render}.test.tsx

Want the full playbook as context for your AI coding assistant?