Anthony Chilaka monogram
LinkedInUpworkWhatsAppX
Blog

Why This Site Moved From a Single-Page App to a True Multi-Page Build

A single index.html and client-side routing look modern, but they don't fix the one thing that actually matters for search and AI crawlers: content that exists before JavaScript runs.

Anthony Chilaka·

This site's SPA had one index.html and hash-anchor navigation — /#about, /#services, /#portfolio. It worked fine for people. It didn't work for crawlers.

The problem client-side routing doesn't fix

A client-side router like React Router fixes bookmarking and the back button. It does not fix the underlying issue: content only exists after JavaScript executes. A crawler that doesn't run your JS — or times out before it finishes — sees an empty shell. The fix isn't a smarter router. It's not having a router at all for content that should be crawlable on its own: a true multi-page build, one static HTML entry per route, built with Vite's multi-entry mode.

What changed, concretely

Each page now gets its own entry point instead of sharing one bundle:

TypeScriptCopy
export function pushPageView(): void {
  window.dataLayer = window.dataLayer || []
  window.dataLayer.push({
    event: 'page_render_mode_set',
    page_render_mode: 'mpa',
  })
}

That page_render_mode field is what lets the analytics side of this project tell the SPA and MPA apart in the same BigQuery table, so the before/after comparison is measured, not assumed.

What's still in progress

The route-by-route migration is incremental on purpose — each page gets scaffolded, verified in a dev server, and approved before the next one starts. This blog, and the MDX pipeline that renders it, is one of those pieces.