Skip to content

Why Astro & Islands

The problem: shipping a framework to render text

Section titled “The problem: shipping a framework to render text”

Most of the web is content: blogs, docs, marketing sites, e-commerce listings. Building those with a client-rendered SPA means the browser downloads a large JavaScript bundle, boots the framework, and re-renders — on the client — HTML that could have been sent as plain HTML in the first place.

The cost is real: slower first paint, delayed interactivity (the “uncanny valley” where the page looks ready but doesn’t respond), and a lot of CPU spent hydrating parts of the page that are never interactive — a footer, an article body, a nav bar.

The insight Astro is built on: for content sites, the HTML is the product. JavaScript should be the exception, not the substrate.

Astro renders the whole page to static HTML, then treats each interactive component as an island — a small, independently-hydrated piece in a sea of static HTML.

flowchart TB
  page["Page: static HTML (no JS)"]
  page --> nav["nav — static"]
  page --> article["article body — static"]
  page --> search["SearchBox — island (hydrated)"]
  page --> cart["CartWidget — island (hydrated)"]
  page --> footer["footer — static"]
A page is static HTML with a few hydrated islands

Two properties define an island:

  • Isolated. Each island hydrates on its own, with its own JavaScript, independent of the others. A slow-to-load carousel doesn’t block a search box.
  • Opt-in. Nothing is interactive unless you mark it so (with a client directive — a whole later lesson). The default is static HTML with no JavaScript at all.

Contrast this with a SPA, where the entire page is one JavaScript application that must boot before anything works. Islands ship JavaScript only for the components that need it, and only that much.

This is the headline: an Astro component, by default, produces HTML and CSS and no JavaScript. Write a component with a button and an onClick, and — unless you turn it into an island — the button renders, but the handler never ships. That’s not a limitation; it’s the point. You’re forced to be deliberate about what runs on the client.

---
// This component runs on the server to produce HTML.
// None of this JavaScript is sent to the browser.
const posts = await getPosts();
---
<ul>
{posts.map((post) => <li>{post.title}</li>)}
</ul>
<!-- Ships: an HTML <ul>. Ships: no JavaScript. -->

The result is pages that are fast by construction: the browser gets HTML it can paint immediately, and downloads JavaScript only for the islands you explicitly opted into.

When Astro is (and isn’t) the right tool

Section titled “When Astro is (and isn’t) the right tool”

Astro shines for content-driven sites: blogs, documentation, marketing, portfolios, e-commerce, anything where most of the page is content and interactivity is localized.

It’s a weaker fit for highly interactive app-like UIs — a figma-style editor, a trading dashboard, a realtime collaborative tool — where nearly everything is stateful and interactive. There, a full SPA framework (or Astro with very large islands, which defeats the purpose) makes more sense. Astro even lets you embed those framework components as islands when you need them.

What is the main cost of building a content site as a client-rendered SPA?
What are the two defining properties of an island?
By default, how much JavaScript does an Astro component ship?
Which project is the WEAKEST fit for Astro?