How I kept this blog fast without giving up React
I used to think SSG and rich interactivity were mutually exclusive. Astro's island architecture let me keep most of the site static and add JavaScript only where it earns its keep.
This blog has interactive physics playgrounds, animated state flow visualizations, and React components with real state. It also stays lightweight because most of the content is still static HTML, and the JavaScript that does ship is attached to specific interactive pieces instead of the whole page.
That is not about chasing a purity metric like “zero JavaScript.” It is about being intentional with what runs in the browser.
I’m an iOS engineer by trade. When I decided to build a technical blog, my first instinct was to reach for Next.js — it’s what everyone recommends, and I already know React. I spun up a project, wrote a few pages, and deployed a version that still leaned on the usual client-heavy React model. Then I opened the Network tab.
The blog index — a list of article titles and dates with absolutely zero interactivity — was shipping 187KB of JavaScript in my setup. The browser was downloading React, React DOM, routing code, and the client-side pieces of my UI just to render static text.
On iOS, I’d never ship a framework to render a UILabel. Why was I doing it on the web?
The Epiphany
The fundamental problem with using a fully hydrated React app model for a content site is that you’re paying the JavaScript tax on content that doesn’t need JavaScript.
In the kind of client-heavy React setup I started with:
- Server sends HTML for the page (from an app shell or a pre-rendered route)
- Browser downloads ~150KB+ of JavaScript (React runtime + your code)
- JavaScript parses and executes (this takes 200-500ms on mobile devices)
- React hydrates the DOM, attaching event listeners to the already-rendered HTML
- The page is now interactive
For my blog index — a list of links — steps 2-4 are pure waste. The page was already interactive the moment the HTML arrived. Links work without JavaScript. The browser has had clickable anchor tags since 1993.
But my TCA visualization? My spring animation playground? Those genuinely need React. They have complex state, event handlers, and animated UI. The question was: how do I get the best of both worlds?
Astro’s Answer: Islands
Astro flips the SPA model on its head. Instead of “everything is JavaScript by default, opt-out where you can,” Astro says “nothing is JavaScript by default, opt-in where you need it.”
When I write a blog post in Astro, this happens at build time:
- Astro compiles my Markdown to HTML
- That HTML is written to a static
.htmlfile - JavaScript is only added for the components I explicitly hydrate
The result: the article content itself loads as plain HTML. The browser renders the content immediately, and Astro only sends client-side code for the parts I have deliberately marked as interactive.
But then I embed an interactive component:
---
// This runs at build time only
import InteractiveStoreDemo from '../../components/interactive/InteractiveStoreDemo.tsx';
---
Here's how TCA works in practice:
<InteractiveStoreDemo client:visible />
And here's why that matters for testing...
Here is that component, live:
The TCA loop
View
Reducer
The only place state changes
Tobias Reinsch