Skip to content

Latest commit

 

History

History
104 lines (76 loc) · 4.11 KB

File metadata and controls

104 lines (76 loc) · 4.11 KB

SEO Improvement Plan for ooxml.dev

Context

ooxml.dev gets ~270 sessions/quarter with only 40 from organic search. But organic visitors have excellent engagement (3+ min avg, 12% bounce) — proving the content is valuable. The problem is zero /docs/* pages get organic traffic because Google can't index a client-side SPA. All doc content is static (defined in docs.ts), making pre-rendering straightforward.

Approach: Build-time pre-rendering + SEO metadata

Use react-dom/server's renderToString in a custom build script to generate static HTML for each route. No framework migration, no new runtime dependencies.

Implementation

1. Pre-render script (apps/web/scripts/prerender.tsx)

Runs after vite build:

  1. Reads dist/index.html as template (has hashed CSS/JS from Vite)
  2. For each route, renders the page component to HTML via renderToString
  3. Injects per-page <title>, <meta>, OG tags, canonical URL, JSON-LD into <head>
  4. Writes to correct path (e.g., dist/docs/tables/index.html)
  5. Client-side React still loads and takes over for interactivity

Routes to pre-render:

  • / (Home)
  • /mcp (MCP page)
  • /spec (shell only — content is API-dependent)
  • /docs + all 7 doc pages from docs.ts

SuperDocPreview handling: Add typeof window === 'undefined' guard to render the XML as a static <pre><code> block during SSR. React hydrates the interactive version client-side.

2. SEO metadata (apps/web/src/data/seo.ts)

Per-route metadata:

  • <title> — e.g., "OOXML Tables (w:tbl) — Structure & Implementation | ooxml.dev"
  • <meta name="description"> — from docs.ts descriptions
  • <link rel="canonical">https://ooxml.dev{path}
  • Open Graph tags (og:title, og:description, og:url, og:type)
  • Twitter card meta

For doc pages, auto-generate from docs.ts (title, badge, description).

3. Client-side title updates (useDocumentTitle hook)

Simple useEffect hook so browser tab title updates during SPA navigation. Used in DocsPage, Home, Mcp, SpecExplorer.

4. Structured data (JSON-LD)

Injected by prerender script per page:

  • Doc pages: TechArticle schema
  • Home: WebSite schema with SearchAction for /spec?q=

5. Sitemap generation

Generated by prerender script → dist/sitemap.xml:

  • All routes with <loc>, <changefreq>, <priority>
  • Auto-includes new doc pages from docs.ts

6. robots.txt (apps/web/public/robots.txt)

User-agent: *
Allow: /
Sitemap: https://ooxml.dev/sitemap.xml

Plus existing AI crawler blocks.

7. Build script update

- "build": "tsc && vite build"
+ "build": "tsc && vite build && bun scripts/prerender.tsx"

Files to create/modify

File Action
apps/web/scripts/prerender.tsx Create — core prerender + sitemap generation
apps/web/src/data/seo.ts Create — per-route SEO metadata
apps/web/src/hooks/useDocumentTitle.ts Create — client-side title hook
apps/web/src/components/SuperDocPreview.tsx Modify — add SSR fallback
apps/web/src/pages/docs/Page.tsx Modify — use useDocumentTitle
apps/web/src/pages/Home.tsx Modify — use useDocumentTitle
apps/web/src/pages/Mcp.tsx Modify — use useDocumentTitle
apps/web/src/pages/SpecExplorer.tsx Modify — use useDocumentTitle
apps/web/public/robots.txt Create
apps/web/package.json Modify — update build script

Verification

  1. bun run build from apps/web/
  2. Inspect dist/docs/tables/index.html — should contain full HTML content, correct <title>, meta tags, JSON-LD
  3. Inspect dist/sitemap.xml — should list all routes
  4. Serve dist/ locally (bunx serve dist) and verify:
    • Pages load with correct content before JS executes (disable JS in browser)
    • Interactive features (SuperDocPreview, spec search) work after JS loads
    • SPA navigation updates browser tab title
  5. Deploy and submit sitemap to Google Search Console

Post-deploy

  • Set up Google Search Console if not already done
  • Submit sitemap
  • Request indexing for key doc pages
  • Monitor indexing progress over 2-4 weeks