Skip to main content
← All Articles · · 10 min read

Astro 5: Islands Architecture Perfected

A deep dive into Astro 5's latest features including server islands, content collections v2, and performance improvements. Learn how Astro's islands architecture delivers exceptional Core Web Vitals and how to use the newest APIs.

astrostatic-siteislandsweb-performance

Astro’s islands architecture was always a clever idea: ship zero JavaScript by default, then add interactivity in isolated “islands” only where needed. The result was fast sites with great Core Web Vitals metrics.

Astro 5 takes this architecture further than any previous version. Server islands, content collections v2, and a redesigned build pipeline push performance to new heights while making the developer experience meaningfully better.

This article covers what’s new, how the key features work, and when to reach for Astro over heavier frameworks.


Refresher: What Are Islands?

In traditional SPAs (React, Vue, Angular), the entire page is a JavaScript application. The browser downloads a large JS bundle, runs it, and then the page becomes interactive. Time to Interactive (TTI) lags behind First Contentful Paint (FCP).

Astro inverts this model. Pages are static HTML by default. JavaScript components—islands—are only added for interactive elements. Each island is independently hydrated.

---
// The component script runs at BUILD TIME only
// No JavaScript is shipped to the browser unless client: directives are used
const posts = await fetch("/api/posts").then(r => r.json());
---

<html>
  <body>
    <!-- Static HTML — zero JS shipped -->
    <h1>Blog Posts</h1>
    {posts.map(post => <article>{post.title}</article>)}

    <!-- Interactive island — ships JavaScript to the browser -->
    <SearchBar client:load />

    <!-- Lazy island — hydrates only when visible in viewport -->
    <CommentSection client:visible />
  </body>
</html>

This produces pages where the majority of content is static HTML, with JavaScript used only where interactivity is genuinely required.


Astro 5: Server Islands

The biggest new feature in Astro 5 is server islands. Standard islands handle client-side interactivity. Server islands handle server-side dynamic content within otherwise static pages.

The problem server islands solve: you have a mostly static marketing page, but one section needs real-time data (user-specific content, live inventory, personalization). Previously you’d either make the whole page dynamic (hurting performance) or fetch the data client-side after load (causing layout shift).

Server islands give you a third option: render that section on the server, asynchronously, without blocking the rest of the page.

---
// pages/product.astro
import ProductDetails from "../components/ProductDetails.astro";
import LiveInventory from "../components/LiveInventory.astro";
import UserRecommendations from "../components/UserRecommendations.astro";
---

<html>
  <body>
    <!-- Static — cached at edge, instant delivery -->
    <ProductDetails product={product} />

    <!-- Server island — renders on-demand on the server -->
    <!-- Shows a placeholder while loading, then replaces with real content -->
    <LiveInventory server:defer productId={product.id}>
      <div slot="fallback">Checking availability...</div>
    </LiveInventory>

    <!-- Another server island with personalized content -->
    <UserRecommendations server:defer userId={userId}>
      <div slot="fallback">Loading recommendations...</div>
    </UserRecommendations>
  </body>
</html>

How it works:

  1. The main page HTML renders immediately (static, cached, fast)
  2. Server islands render asynchronously in parallel on the server
  3. The browser receives the shell HTML, shows fallback content
  4. Server island HTML streams in and replaces the fallback

This pattern delivers excellent Core Web Vitals (LCP is fast because the page shell loads immediately) while still supporting dynamic, personalized content.


Content Collections v2

Astro’s content collections API is how you manage structured content—blog posts, documentation pages, product listings. Version 2 rewrites the system for better type safety, more flexibility, and improved performance.

Defining Collections

// src/content/config.ts
import { defineCollection, z } from "astro:content";

const blog = defineCollection({
  type: "content",  // Markdown/MDX files
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()),
    draft: z.boolean().default(false),
    author: z.string().optional(),
    image: z.object({
      src: z.string(),
      alt: z.string(),
    }).optional(),
  }),
});

const docs = defineCollection({
  type: "content",
  schema: z.object({
    title: z.string(),
    description: z.string(),
    sidebar: z.object({
      label: z.string().optional(),
      order: z.number().optional(),
    }).optional(),
  }),
});

// Data collections for JSON/YAML files
const authors = defineCollection({
  type: "data",
  schema: z.object({
    name: z.string(),
    bio: z.string(),
    avatar: z.string(),
    twitter: z.string().optional(),
  }),
});

export const collections = { blog, docs, authors };

Querying Collections

---
// src/pages/blog/index.astro
import { getCollection } from "astro:content";

// Fully typed — TypeScript infers schema from config.ts
const allPosts = await getCollection("blog");

// Filter and sort
const publishedPosts = allPosts
  .filter(post => !post.data.draft)
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

// Get posts with specific tag
const devToolsPosts = allPosts.filter(
  post => post.data.tags.includes("dev-tools")
);
---

<html>
  <body>
    <ul>
      {publishedPosts.map(post => (
        <li>
          <a href={`/blog/${post.slug}`}>{post.data.title}</a>
          <time>{post.data.pubDate.toLocaleDateString()}</time>
        </li>
      ))}
    </ul>
  </body>
</html>

Rendering Collection Entries

---
// src/pages/blog/[slug].astro
import { getCollection, getEntry } from "astro:content";

export async function getStaticPaths() {
  const posts = await getCollection("blog", ({ data }) => !data.draft);
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content, headings } = await post.render();
---

<html>
  <head>
    <title>{post.data.title}</title>
    <meta name="description" content={post.data.description} />
  </head>
  <body>
    <article>
      <h1>{post.data.title}</h1>
      <Content />
    </article>

    <!-- Table of contents from headings -->
    <nav>
      {headings.map(h => (
        <a href={`#${h.slug}`} style={`padding-left: ${(h.depth - 2) * 16}px`}>
          {h.text}
        </a>
      ))}
    </nav>
  </body>
</html>

Performance Wins in Astro 5

Improved Build Pipeline

Astro 5 rewrites the build system for faster production builds:

  • Incremental builds: Only rebuilds pages that changed, not the entire site
  • Parallel rendering: Multiple pages render simultaneously during build
  • Better asset optimization: Images are processed more efficiently

For a site with 500 pages, build times that took 3-4 minutes in Astro 4 now complete in under a minute.

View Transitions API Integration

Astro 5 deepens its integration with the browser’s View Transitions API for smooth page navigation:

---
// src/layouts/Layout.astro
import { ViewTransitions } from "astro:transitions";
---

<html>
  <head>
    <!-- Enables smooth page transitions -->
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>
---
// Animate specific elements across page transitions
---

<img
  src={post.image}
  alt={post.title}
  transition:name={`hero-${post.slug}`}
/>

When navigating between pages that share elements with the same transition:name, the browser animates the transition smoothly—no JavaScript framework required.

Core Web Vitals Impact

A typical Astro 5 site’s Lighthouse scores:

MetricAstro 5React SPANext.js (SSR)
LCP0.8-1.2s2-4s1-2s
FID/INP<50ms100-300ms50-150ms
CLS00.1-0.30.05-0.1
JS Bundle0-10KB150-500KB80-200KB

Approximate values for typical content sites—actual results depend on implementation.

The zero-JS default means Astro sites start with an enormous performance advantage. Every interactive feature you add is a deliberate choice, not a framework tax.


When to Use Astro

Excellent fit:

  • Marketing sites and landing pages
  • Documentation sites (Astro is used by MDN, many open-source projects)
  • Blogs and content-heavy sites
  • E-commerce product pages (static shell + server islands for inventory/personalization)
  • Portfolio sites

Consider alternatives when:

  • Your app is primarily a dashboard or admin interface (heavy interactivity everywhere)
  • Real-time collaborative features are central (live cursors, collaborative editing)
  • You need complex client-side state management across many views

For content-first sites, Astro 5 is the strongest choice available. The combination of zero-JS defaults, server islands for dynamic content, and excellent TypeScript tooling makes it uniquely well-suited for the modern web.


Getting Started with Astro 5

# Create a new Astro project
npm create astro@latest my-site

# Add integrations
npx astro add react   # React islands
npx astro add tailwind
npx astro add sitemap
npx astro add mdx

# Start development server
npm run dev

The default template includes content collections, TypeScript configuration, and a sample blog. From there, the upgrade path to server islands is straightforward when you need dynamic content.


Explore more frontend tools and frameworks at DevPlaybook.

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.