Thulani Maseko

Portfolio site: React SPA + 3D scroll + edge SSR for SEO

282 words · ~1 min read portfolio · vite · react · three.js · cloudflare-workers · seo

Self-hosted portfolio built on Vite + React 18 + @react-three/fiber, deployed to Cloudflare Workers. Static content pages generated from markdown so AI engines can cite individual posts.


The site you're reading right now. Vite + React 18 SPA with a @react-three/fiber scroll-bound scene on the home, plus statically-generated content pages for /blog/* and /answers/* so crawlers and AI engines see real HTML instead of an empty <div id="root">.

Architecture

┌─────────────────────────────────────────────────┐
│        Cloudflare Workers (single global)        │
│                                                  │
│   ┌────────────────────────────────────────┐    │
│   │  src/worker.ts (header middleware)     │    │
│   │  - HSTS, CSP, Permissions-Policy       │    │
│   │  - X-Robots-Tag: noai, noimageai       │    │
│   └─────────────────┬──────────────────────┘    │
│                     │                            │
│                     ▼                            │
│   ┌────────────────────────────────────────┐    │
│   │  [assets] - static files from dist/    │    │
│   │  ├── index.html       (SPA shell + 3D) │    │
│   │  ├── blog/<slug>/     (static HTML)    │    │
│   │  ├── answers/<slug>/  (static HTML)    │    │
│   │  ├── projects/<slug>/ (static HTML)    │    │
│   │  ├── sitemap.xml                       │    │
│   │  ├── rss.xml                           │    │
│   │  └── assets/*.js, *.css, *.woff2       │    │
│   └────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘

Build pipeline

vite build
  -> Vite + Rollup -> dist/index.html + chunked assets
                       (vendor splits: react, three, radix, motion)
node scripts/build-static-pages.mjs
  -> reads content/{blog,answers,projects}/*.md via unified + remark
  -> writes dist/<kind>/<slug>/index.html with JSON-LD per page
  -> emits dist/content-manifest.json
node scripts/build-sitemap.mjs
  -> static routes + manifest -> dist/sitemap.xml
node scripts/build-rss.mjs
  -> manifest -> dist/rss.xml
node scripts/inject-verification.mjs   # GSC + Bing tokens
node scripts/inject-fingerprint.mjs    # build identifier for theft detection

SEO posture

Bundle budgets

Enforced by size-limit in CI (.github/workflows/bundle-size.yml):

Chunk Limit Actual
main JS (app code) 90 KB gz ~78 KB
react-vendor 60 KB gz ~53 KB
three-vendor 300 KB gz ~277 KB
radix-vendor 35 KB gz ~31 KB
motion-vendor 30 KB gz ~25 KB
CSS 40 KB gz ~35 KB

What's deliberately NOT here

See also