← Back to Archive

Migrating to Astro

Why

The site was a React SPA — everything, including the nav and page shell, was rendered by JavaScript. This caused visible layout shift on every page load: the nav would pop in after hydration, fonts would settle, widgets would jump into place. Fine functionally, but jarring.

The goal was a page that loads like it was never not there.

What changed

Migrated to Astro with React islands. Static content — nav, profile, bio, skills — is now plain HTML from byte one. Interactive parts (weather, Spotify, mood, clock, command palette) stay as React components but only hydrate where needed.

Key wins:

  • Nav is in the initial HTML, zero JS required for it to appear
  • Active link state is resolved at build time via Astro.url.pathname
  • Theme toggle appearance is CSS-driven ([data-theme] on <html>), set by an inline script before first paint — no flash, no animation on load
  • compute at build time, not runtime, more pure HTML, less js

Infra unchanged: Cloudflare Pages + Workers handles the API backend the same way. The Astro output is static, Workers serve the dynamic API routes (/api/*).

Trade-offs

Blog post content renders in Astro templates, not React islands — MDX’s JSX runtime doesn’t mix cleanly with React islands. A minor constraint with no user-visible impact.