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
- Astro renders the page, replacing server islands with fallback content
- The page ships to the browser with small inline scripts that fetch each island
- Each island makes a request back to the server
- The server renders the island component and returns HTML
- 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 islands | Client islands | |
|---|---|---|
| Runs on | Server | Browser |
| Directive | server:defer | client:load, client:visible, etc. |
| JS shipped | None (HTML injection) | Framework runtime + component code |
| Use for | Dynamic server content, personalization | Interactive UI (forms, toggles, animations) |
| Component type | .astro only | React, 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)