Skip to content

Choosing Astro: Between Documents and Applications

Choosing a website structure usually involves a trade-off: do you build a highly responsive app or keep the JavaScript weight low? It used to be a black-and-white choice.

3 min read
Astro
React
Islands
Hydration
MPA
Frontend

Choosing a website structure usually involves a trade-off: do you build a highly responsive app or keep the JavaScript weight low? It used to be a black-and-white choice: use a Single Page Application (SPA) like React for complex tools, or stick to static HTML for content sites. Now, Astro offers a middle ground through "islands architecture."

I often find myself pairing Astro with React when the core of the product is essentially a document that just needs "pockets" of interactivity. Think of a marketing site with a price calculator, or a blog that needs a search bar and a theme toggle. With this approach, most of the page stays as pure HTML, and you only give specific components the ability to run on the client via partial hydration. There’s no point in shipping the entire React runtime to the browser if you only need a single interactive button.

That said, this isn't a silver bullet. If I’m building a heavy dashboard, a complex editor, or any tool that relies heavily on state across multiple pages, Astro might actually get in the way. For those cases, using Vite SSR or a framework designed specifically as an SPA makes much more sense for the sake of workflow efficiency.

The island concept in Astro is very straightforward. You decide exactly when a component wakes up using specific directives. You can load it immediately, wait until the user scrolls to it (client:visible), or trigger it only when certain media queries are met. If you don't give it an instruction, it just stays as static HTML without sending any raw JavaScript to the client. That’s the whole point.

Moving from Vite SSR to Astro is really a shift in mindset. In Vite SSR, you’re still following an SPA architecture with a bit of server help. In Astro, the logic is flipped: the document is the priority, and React is just a "guest" invited only when necessary.

I’ve made the mistake of trying to hydrate too much of a page using client:load just because I was too lazy to set boundaries. I ended up with an inefficient SPA and a more complicated navigation system. I didn't fully realize how much bundle size would tank performance if I didn't strictly manage the boundaries between components.

The key is to keep every "island" small. Don't end up building one giant component that swallows the whole page; if you do that, you're just falling back into the same old patterns we were trying to avoid.