Astro
01 / 02

Components, Islands & Client Directives

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