Server Islands

Server islands defer expensive server-side rendering out of the main page render. They let you serve the page shell immediately while slower or personalized content loads in parallel.

The problem they solve

Consider a news page that is mostly static but has a personalized user avatar and dynamic ad slots. Without server islands, you either:

  • Render everything on-demand (slow initial response)
  • Cache the whole page (cannot personalize)
  • Use client-side JS to load personalized parts (flash of empty content, extra requests)

Server islands let you cache the main page content while the personalized parts render server-side in parallel.

Basic usage

Add server:defer to any Astro component:

---
import Avatar from '../components/Avatar.astro';
import AdSlot from '../components/AdSlot.astro';
---
<header>
  <nav>Site Nav</nav>
  <Avatar server:defer />
</header>

<main>
  <article>
    <!-- Static article content renders immediately -->
    <h1>{story.title}</h1>
    <p>{story.body}</p>
  </article>
  <aside>
    <AdSlot server:defer />
  </aside>
</main>

The page shell renders and ships to the browser. The Avatar and AdSlot components render on the server in parallel and stream in when ready.

Fallback content

Show placeholder content while the island loads:

<Avatar server:defer>
  <img src="/default-avatar.png" alt="Loading..." slot="fallback" />
</Avatar>

The fallback renders immediately in the HTML. When the server island resolves, it replaces the fallback. No layout shift if you size the fallback correctly.

How it works

  1. Astro renders the page, replacing server islands with fallback content
  2. The page ships to the browser with small inline scripts that fetch each island
  3. Each island makes a request back to the server
  4. The server renders the island component and returns HTML
  5. The browser swaps the fallback for the real content

This is conceptually similar to React’s <Suspense> boundaries but happens at the HTTP level, not the React tree level.

Use cases

  • User avatars / login state on otherwise cacheable pages
  • Dynamic pricing on a product page
  • Personalized recommendations alongside static content
  • Real-time data widgets (stock tickers, live scores)
  • A/B test variants without cache-busting the whole page

Server islands vs client islands

Server islandsClient islands
Runs onServerBrowser
Directiveserver:deferclient:load, client:visible, etc.
JS shippedNone (HTML injection)Framework runtime + component code
Use forDynamic server content, personalizationInteractive UI (forms, toggles, animations)
Component type.astro onlyReact, Svelte, Vue, etc.

They solve different problems. Server islands handle dynamic server content. Client islands handle interactive browser UI. A page can use both.

Caching pattern

The powerful pattern: cache the outer page aggressively, let server islands handle the dynamic parts:

---
// Page-level cache header (long TTL)
Astro.response.headers.set('Cache-Control', 'public, s-maxage=3600');
---
<Base title="Product Page">
  <!-- Static: cached for 1 hour -->
  <ProductDetails product={product} />

  <!-- Dynamic: fetched fresh per request -->
  <UserReviews server:defer productId={product.id}>
    <p slot="fallback">Loading reviews...</p>
  </UserReviews>
</Base>

Limitations

  • Server islands must be Astro components (.astro), not framework components
  • Each island makes a separate HTTP request back to the server
  • Adds request overhead (mitigated by parallel loading)
  • Not suitable for content that must be in the initial HTML for SEO (search engines may not wait for deferred content)