Astro introduced a powerful new pattern called Server Islands last year. If you're building with Astro, it's worth understanding what they are, how they work, and where they shine (and where they don’t). Their documentation explains server islands really well, but I wanted to dig a little deeper on the technical side of things.
What Are Server Islands?
Server Islands allow you to render a portion of your page on the server on-demand, without blocking the rest of the page load.
Think of your page as the ocean, mostly static or cached HTML. Now imagine dynamic content rising out of that ocean like islands appearing after the tide goes out. These "server islands" let you render just the parts of the page you need from the server, without slowing down the rest.
This pattern is useful for:
- Personalized greetings
- Authenticated user info (avatars, session data)
- Region-specific content (prices based on country)
- Anything dynamic that shouldn’t block first render
Unlike traditional Server-Side Rendering (SSR) where the whole page waits for server data, Server Islands let you defer just part of the page until after load.
Server Islands were partially inspired by Next.js’s Partial Pre-Rendering (PPR), which similarly balances static and dynamic content by deferring specific sections to load asynchronously.
How They Work
The API is simple: you use the server:defer
directive on an Astro component.
Example:
<SomeServerIsland server:defer />
This tells Astro to replace that component with a placeholder and fetch the real HTML immediately from the server after the page loads. You can also provide a fallback UI that renders in the initial page load server-side while the island is being fetched:
<SomeServerIsland server:defer>
<div slot="fallback">Loading some server island...</div>
</SomeServerIsland>
What actually happens under the hood:
At build time, Astro replaces the island with a small script.
Here's an expanded view:
<!--[if astro]>server-island-start<![endif]-->
<h1>Hello
World</h1>
<link rel="preload" as="fetch" href="/_server-islands/SomeServerIsland?e=default&p=&s=%7B%7D" crossorigin="anonymous">
<script type="module" data-astro-rerun data-island-id="137eec60-035b-4630-b4af-365acd683b97">let response = await fetch('/_server-islands/SomeServerIsland?e=default&p=&s=%7B%7D');
replaceServerIsland('137eec60-035b-4630-b4af-365acd683b97', response);</script>
The script fetches the HTML for that component from a special server endpoint (e.g. /_server-islands/SomeServerIsland?e=default&p=&s=%7B%7D
) and replaces the node which may or may not have fallback markup with the rendered HTML once the response is ready.
Here’s the client-side function that makes that swap happen:
async function replaceServerIsland(id, r) {
let s = document.querySelector(\`script[data-island-id="\${id}"]\`);
// If there's no matching script, or the request fails then return
if (!s || r.status !== 200 || r.headers.get('content-type')?.split(';')[0].trim() !== 'text/html') return;
// Load the HTML before modifying the DOM in case of errors
let html = await r.text();
// Remove any placeholder content before the island script
while (s.previousSibling && s.previousSibling.nodeType !== 8 && s.previousSibling.data !== '[if astro]>server-island-start<![endif]')
s.previousSibling.remove();
s.previousSibling?.remove();
// Insert the new HTML
s.before(document.createRange().createContextualFragment(html));
// Remove the script. Prior to v5.4.2, this was the trick to force rerun of scripts. Keeping it to minimize change to the existing behavior.
s.remove();
}
A Fun Discovery
Someone in the chat during my livestream asked if Server Islands work inside client components. I mentioned that a Server Island has to be an Astro component, so you can't directly embed one inside a client component like React, Vue, Svelte, or Solid. But I suspected it would probably work if passed as a child.
Believe it or not, it does. Embedding Server Islands inside client components works as long as you pass them as children.
And it makes sense if you think about it. Astro renders the Server Island as plain HTML, so the client-side component renders its markup on the server as usual. The page loads, the Server Island hydration script kicks in and updates the placeholder, and then the client component bootstraps its interactivity like normal.
Limitations and Gotchas
Server islands only work with
.astro
components, not React, Vue, Svelte, Solid, etc.You can't place a server island directly inside a client component, but you can pass it as a child.
You must set
output: "server"
in your Astro config.During development, no adapter is required—but for deployment you'll need one (e.g. Netlify, Vercel, Node).
JavaScript must be enabled in the browser for the islands to render properly.
Where Can You Use Them?
Ideal for:
Showing personalized or location-based content
Displaying user data post-login
Incrementally enhancing static pages
SEO-friendly dynamic blocks (e.g., “related articles,” recent comments)
Not ideal for:
Entirely dynamic pages
JS-disabled environments
Components that require full client-side interactivity before page load
Wrapping Up
Server Islands are a great way to blend static-first performance with dynamic, server-rendered content. They're lightweight, easy to use, and a smart alternative to full-page SSR when you only need to personalize a section of the page.
If you're already using Astro, this is a feature worth exploring—especially if you're serving authenticated content or want to progressively enhance your pages.
All you need is server:defer
, and you’re off to the islands. 🏝️ Learn more about server islands in the official Astro documentation.
Watch the full walkthrough:
Want to connect?
All my socials are at nickyt.online
Cover image by Marek Okon on Unsplash
Top comments (18)
Beautiful. Astro originally invented the Dynamic Islands concepts. React took it and make RSC where client components are the islands.
Nextjs invented PPR last year to improve SSR performance of slow server components.
Astro took it from them and made server islands which are deferred and until loaded show a fallback slot component. Life is full circle in this world lol I love web dev!
Glad you enjoyed the blog post! And yes, the web is great!
Small correction in Astro inventing islands. It actually came from a meeting between Preact creator, @_developit and at the time frontend architect at Etsy Katie Sylor-Miller.
See Islands Architecture
I think that Katie Sylor-Miller might have moved to BlueSky bsky.app/profile/ksylor.bsky.social since the twitter handle is not finding an account
Thanks Esther!
Oh wow. Didn’t know that. That’s cool. Will check out this article thank you!
Could not find any source/reference on the requirement of setting output=server for Server Islands. Is that true?
Thanks for reading Rong Sen!
It doesn't appear to be referenced in the docs. I think it's implied by the fact you are doing something server-side, but it could be a great documentation PR to update that if it is indeed missing.
Really appreciate how you broke down the server:defer behavior and those child-component gotchas - cleared up a bunch for me. Anyone else experimenting with mixing server islands and client frameworks?
Thanks for the kind words. Glad you enjoyed the blog post!
wow, this is super helpful - i always wonder if using more server stuff actually makes building easier or just adds more stuff to manage long run you ever feel like these things make projects simpler or just different kind of headache
Thanks Nathan! The nice thing about this one is @ascorbic on the Astro core team made the api for this dead simple so that aside from setting your Astro config to
output: server
, it's just aserver:defer
directive on an Astro component. 🔥Just realised how the server islands inside Astro components nested as children works the same way as nesting RSC server components inside client components. It’s coz the defered async HTML is received later and slotted in similar to how the RSCpayload for nested server components are streamed in
Great post
Thanks!
If the request has a session you can generate pages with personalized/localized parts on the server, no islands needed. This is the way backend frameworks have worked for years.
The biggest problem is controlling the cache if you do this. The main benefit is no javascript and subrequests.
I would not recommend to use it for related or recent blocks if the link is only the main subject of the page. If it is combined with location or user then I agree.
I know the concept from Drupal as BigPipe which comes from FaceBook. And they got it from the way microprocessors work. There is nothing new about it :)
Good information given
Thanks!
Great article and video thank you for sharing I am using Astro more and more 👍👍