← Writing
The Editorial · Engineering

Islands architecture - or why reading three thousand words means downloading a whole app

Issue 026Aug 6, 202610 min read

You open an article on your phone. The words are there in a second. You tap the menu and nothing happens for another three. The page was finished - it just wasn't awake yet, because your phone was busy rebuilding, from scratch, the same page the server had already built and sent you. Islands architecture is the idea that a page mostly made of text should be sent as text, with a few small powered zones for the parts that actually respond to a tap. Here's what that means, where it pays, and where it's exactly the wrong shape.

The issue

You open a news article on your phone, on ordinary mobile data. The headline and the first paragraphs are there almost immediately - good, fast, nothing to complain about. Then you tap the menu. Nothing. You tap it again, slightly harder, the way everybody does. Two or three seconds later the menu opens, and possibly the page jumps because something else finished loading underneath your thumb.

The page was finished. You could read every word of it. It just wasn't awake yet, and the reason why is the single strangest thing about how modern websites are built - strange enough that it's worth explaining properly even if you never write a line of code.

The page gets built twice

A server somewhere ran your app, produced a finished HTML page, and sent it to your phone. That's why the words appeared quickly. But the buttons in that HTML aren't connected to anything - HTML is paper. So the site then sends your phone all the JavaScript that would have built that page, and your phone downloads it, parses it, and runs the whole thing again, on top of the page that's already there, purely to work out which handler belongs to which button.

That second build has a name - hydration - and it happens on the slowest computer in the entire chain. Not the server. Not your laptop. Somebody's four-year-old Android, on a train, on 4G. The gap you felt when you tapped the menu is that.

Fig 1The words arrive early and the page looks done. Nothing responds until the browser has finished rebuilding it.AI-generated figure

And the crucial detail: by default, hydration is all or nothing. The page is one component tree, so the browser walks the whole tree. Newer React can reorder that walk - a section wrapped in a Suspense boundary hydrates on its own, and tapping something that isn't awake yet pushes it to the front of the queue. But notice what reordering doesn't change: the code for the comments was still downloaded, still gets parsed, still runs. You can change the order of the bill. You can't decline it.

The observation

So here's the question islands architecture asks, and once you've heard it you can't stop applying it to every site you open: how much of this page actually responds to a tap?

On a news article - the menu toggle, the like and share row, the comment box, maybe an embedded video. That's it. The headline is text. The byline is text. Three thousand words are text. The images are images. The related-articles list at the bottom is five links. None of it will ever respond to anything, and all of it got rebuilt anyway, because it was in the tree.

Fig 2Colour a content page by what genuinely needs JavaScript and almost all of it goes grey - but the grey part is what you were paying for.AI-generated figure

The name comes from that picture. The interactive bits are islands in a sea of static HTML. Send the sea as HTML - it's already correct, it needs nothing - and give each island only the code it needs. What lands on the phone is a newspaper with a few screens set into it, rather than a screen that spends its first three seconds pretending to be a newspaper.

What that looks like

Astro is the reference implementation, and the syntax is almost embarrassingly direct. You mark a component with when it should come alive, and everything you don't mark is simply HTML forever.

src/pages/blog/[slug].astroAstro
---
import Menu from "../../components/Menu.jsx";
import LikeButton from "../../components/LikeButton.jsx";
import Comments from "../../components/Comments.jsx";
import RelatedPosts from "../../components/RelatedPosts.astro";
const post = await getPost(Astro.params.slug);
---

<Menu client:load />

<article>
  <h1>{post.title}</h1>
  <Fragment set:html={post.html} />
</article>

<LikeButton   client:idle    count={post.likes} />
<Comments     client:visible postId={post.id} />
<RelatedPosts posts={post.related} />
Fig 3Instead of one pass over everything, three small ones that each start when there's a reason to.AI-generated figure

The best real-world comparison for client:visible is the escalator at a metro station that idles until someone steps on the plate. You don't run every escalator in the building all day for the handful of people who'll use them in the next hour. You start each one when somebody arrives.

Three things follow from the model that are worth knowing even secondhand. Each island is its own root - two islands on the same page don't share a component tree or a state store unless you deliberately wire one up. They hydrate in parallel and independently, not in one top-down sweep, so a heavy island can't hold up a light one. And the HTML is the real thing, not a preview of the real thing - which is why this shape is quietly excellent for search engines and screen readers, for the same reasons Issue 021 goes into.

Server islands: the one that saves your CDN bill

There's a newer half of this worth knowing about, because it fixes a problem almost every product hits. You have a product page that is identical for everyone - the photos, the description, the price, the reviews - except for one small box in the corner that says "recommended for you" or shows the logged-in user's avatar. That one box means the page is different for every visitor, which means the CDN can't cache it, which means every single view goes all the way back to your server. One widget, and you're paying to render a page that hasn't meaningfully changed in three weeks.

One thing to settle before you get attached to it: this half needs a server. Server islands are deferred renders, not deferred JavaScript, so the site has to be on-demand rendered with an adapter - a purely static build has nothing to defer to. That's the trade. You give up "it's just files" and get back a page whose cacheable 95% is genuinely cacheable.

server-islands.astroAstro
<!-- the page is now cacheable again; these two fill in after -->
<Avatar server:defer>
  <GenericAvatar slot="fallback" />
</Avatar>

<Recommendations server:defer>
  <p slot="fallback">Picking something out for you…</p>
</Recommendations>

<!-- Recommendations.astro reads the cookie itself, on the server:
       const uid = Astro.cookies.get("uid")?.value;
     Don't pass it down as a prop. Props are encrypted into the page's
     HTML, which is the cached HTML, so every visitor would arrive
     holding whichever uid was there when the page was built. -->
Fig 4Server islands apply the same carving to the server side: cache the 95% that's identical, defer the small piece that isn't.AI-generated figure

Same idea, moved one step back. Islands split the page by what needs JavaScript; server islands split it by what's different per person. Both are just refusing to let a small special case set the price for the whole page.

If you don't write the code

This is the pattern with the clearest commercial edge of any in this series, and it has almost nothing to do with elegance.

Your marketing site, your pricing page, your blog, your docs and your changelog are the pages a stranger judges you on and the pages Google reads first. They are also the pages with essentially no interactivity. If they were built with the product's stack because "we already use React and it's one repo," you are paying application performance costs on your acquisition funnel - and the people who feel it hardest are exactly the ones on cheap phones and patchy networks that you were hoping to convert. Rebuild one of those pages on islands and what ships is the search box and the theme toggle - not a framework runtime, a router and the whole component tree on top. The gap there is usually measured in whole multiples, not percentages. That is not a micro-optimisation; that's a different product on a mid-range phone.

The right question in that meeting isn't which framework. It's the one from the top of this issue, pointed at one specific page: how much of this actually responds to a tap? Under ten percent, islands. Over half, don't - and knowing which of your pages is which is a five-minute conversation that saves a quarter of arguing.

When it's the wrong shape entirely

Islands are genuinely bad at some things, and the failure isn't subtle - it's the kind where you notice in week two and it never gets better.

It's also worth knowing that islands aren't the only answer to this complaint. React Server Components keep the single tree and move work to the server instead of abandoning the tree. Qwik skips hydration altogether by recording which handler belongs to which element and fetching it on the first click. Three different answers to the same observation - that most pages ship far more JavaScript than they use - and which one fits depends entirely on how much of your page moves.

For the framework-by-framework detail - Astro's directives in full, Fresh, Marko, and where Server Islands landed - patterns.dev's Islands Architecture writeup is the right next stop. Issue 027 places this alongside the other ways a page can get made, and if you want the measuring side before you change anything, Issue 019 is the one about finding out where the time actually goes.

All issues