Islands and deployment

Keep islands independent

Use small hydration roots so one low-priority widget does not delay unrelated interaction.

Every hydrated component is an island: its own root, with its own JavaScript, hydrated on its own schedule. A menu with client:load and a chart with client:visible don’t know about each other. The chart’s bundle can be slow and the menu still works.

This is different from a single-page app, where one root hydrates the whole page and one slow component blocks everything.

Pass small, serializable props

Independence works best when islands don’t share live state. Give each one the initial data it needs, as props:

<CartButton client:load initialCount={cart.count} />
<Reviews client:visible productId={product.id} />

Astro serializes those props into the HTML so the browser can pick them up. That has two consequences.

First, only serializable values cross. Numbers, strings, booleans, arrays, plain objects, dates. Not functions, not a database client, not a class instance with methods. Pass product.id, not product with its save() method.

Second, props are public. They land in the page source as attributes on the <astro-island> element. If you wouldn’t print it in the HTML, don’t pass it to an island.

When islands need to talk

Sometimes two islands do need to coordinate. An “add to cart” button in one island and a cart count in the header. You have three options, and I’d pick them in this order.

For an occasional message, dispatch a custom event on document:

document.dispatchEvent(new CustomEvent('cart:add', { detail: { id: 42 } }))

The header island listens for cart:add and updates its count. No shared library, and it works across frameworks.

For state that several islands read and write, use a tiny external store. Nano Stores is what the Astro docs recommend. Both islands import the same atom and subscribe to it. It works across React and Vue islands on the same page.

For an interface that changes as one tightly coupled unit, stop splitting it. Make it one island. A checkout form where every field affects every other field is one component, not six islands sending events to each other.

Verify it

Open the Network panel and reload with the page scrolled to the top. The client:visible island’s bundle should not appear until you scroll to it.

Then open the Performance panel, record, and click the client:load island while the visible one is still loading. The click handler should fire without waiting. If it does wait, the two islands share something they shouldn’t.

Lesson completed