Islands and deployment
Render a framework component without hydration
Use React, Vue, Svelte, or another supported component as static HTML when it has no browser interaction.
A framework integration doesn’t force you to hydrate. Use a React component with no client:* directive and Astro renders it to HTML, once, on the server. No React in the browser.
---
import ProductCard from '../components/ProductCard.jsx'
---
<ProductCard name="Notebook" price={12} />
Astro runs React’s server renderer during the build, gets back a string of HTML, and puts it in the page. The visitor downloads a div with a name and a price. Check the Network panel: no React bundle for this component.
This is great for reuse. Maybe you have a ProductCard from a React project that formats prices nicely and handles a dozen edge cases. Drop it in, pass props, done. It’s a template now.
Where it breaks
Anything that needs the browser stops at the render boundary. Here is a card with a click handler:
export function ProductCard({ name }) {
return <button onClick={() => alert(name)}>{name}</button>
}
Without a directive, the browser receives <button>Notebook</button>. React never runs in the browser, so onClick is never attached. Click it. Nothing happens.
This is worse than a static card. The button looks interactive and lies. A visitor clicks twice, assumes the site is broken, and leaves. The same goes for useState, useEffect, and any event handler. They are all browser-side work, and there is no browser side here.
Decide what the element is
When you hit this, don’t reflexively add client:load. Ask what the button does.
If it navigates, make it a link. If it submits, make it a form. Both work with zero JavaScript, and you keep the static render:
export function ProductCard({ name, slug }) {
return <a href={`/products/${slug}/`}>{name}</a>
}
If it really needs local state or a browser API, add the directive. Then check the Network panel again. Now you’ll see the React bundle, and you’ll know why it’s there.
A quick test
Render a framework component without a directive and view the page source. You’ll find its HTML inline, with no wrapper around it. Add client:load and look again. The component is now wrapped in an <astro-island> element with the props serialized as attributes. That wrapper is what hydration hooks into. Its absence is proof the component shipped as plain HTML.
Lesson completed