Astro: Components, Islands & Client Directives
Astro renders to static HTML with zero client-side JavaScript by default -- "islands architecture" means only components explicitly marked interactive ship JS and hydrate, leaving the rest of the page as plain, fast HTML.
Anatomy of an .astro Component
---
// Frontmatter -- runs server-side ONLY (build time or per-request),
// never shipped to the client. Safe to use secrets/DB calls here.
import Layout from '../layouts/Layout.astro';
import Counter from '../components/Counter.jsx';
const { title } = Astro.props;
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
<!-- Template -- HTML-like, {expression} interpolation like JSX -->
<Layout title={title}>
<h1>{title}</h1>
<ul>
{posts.map((post) => <li>{post.title}</li>)}
</ul>
<!-- Static by default: renders to HTML, ships ZERO JavaScript -->
<Counter startCount={0} />
<!-- Opted into hydration: an interactive "island" -->
<Counter startCount={0} client:load />
</Layout>Client Directives
No directive: renders to static HTML, zero client JS, no interactivity -- the default.
client:load: hydrate immediately on page load -- for critical, above-the-fold interactivity.
client:idle: hydrate once the browser is idle (requestIdleCallback) -- lower priority.
client:visible: hydrate once the component scrolls into the viewport -- defers cost for below-the-fold widgets.
client:only="react": skip server-rendering entirely, render purely client-side -- for components touching browser-only APIs (window, localStorage) that would error during SSR.
Multi-Framework Support & Slots
---
// Different islands can use different frameworks on the SAME page,
// each hydrated independently via its own integration
import ReactWidget from '../components/ReactWidget.jsx';
import VueWidget from '../components/VueWidget.vue';
import SvelteWidget from '../components/SvelteWidget.svelte';
---
<ReactWidget client:visible />
<VueWidget client:idle />
<SvelteWidget client:load />
<!-- Layout.astro -- <slot /> marks where child content renders,
like `children` in React or Vue's default slot -->
<html>
<body>
<header>Site Header</header>
<main><slot /></main>
<!-- named slot for a distinct content area -->
<slot name="footer" />
</body>
</html>Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free