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.
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:
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.
