MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Astro 4: Content-First Web Framework

Build with Astro: islands architecture, content collections, view transitions.

AstroSSGIslandsFrontend

By MinhVo

Introduction

The web development world has been dominated by JavaScript frameworks for over a decade. React, Vue, Svelte, and Angular have become the default tools for building websites. But this dominance has come at a cost: JavaScript-heavy websites that load slowly, perform poorly on mobile devices, and ship far more code than necessary for content-focused sites.

Astro challenges this paradigm fundamentally. Built around the principle that websites should ship zero JavaScript by default, Astro delivers lightning-fast performance while still allowing you to use your favorite UI frameworks where interactivity is needed. With Astro 4, the framework has matured into a production-ready tool that powers everything from personal blogs to enterprise documentation sites.

This guide explores Astro 4's architecture, features, and practical implementation patterns. Whether you're building a blog, a marketing site, or a documentation portal, Astro offers a fundamentally better approach to web development.

Web framework architecture

The Problem Astro Solves

The JavaScript Performance Problem

Modern JavaScript frameworks are powerful, but they come with significant overhead:

Bundle size: A typical React application ships 40-100KB of JavaScript before your own code. Add a few dependencies and you're easily at 200-500KB. On a 3G connection, that's 2-5 seconds just to download the JavaScript.

Hydration cost: Frameworks like React and Vue require "hydration"—the browser must parse, compile, and execute the JavaScript before users can interact with the page. This adds 1-3 seconds of delay on mid-range mobile devices.

Over-engineering content sites: A blog post or marketing page is fundamentally static content. Wrapping it in a full JavaScript framework adds complexity without any benefit. The user doesn't need client-side routing, state management, or reactivity to read an article.

Astro's Answer: Ship Less JavaScript

Astro's core insight is that most web pages are static content with small islands of interactivity. Instead of shipping an entire framework runtime for those few interactive components, Astro renders everything to static HTML at build time and only sends JavaScript for the components that actually need it.

Traditional React SPA:
├── 45KB React runtime
├── 30KB React DOM
├── 20KB Router
├── 15KB State management
├── Your application code
└── Total: 150-300KB+ of JavaScript
 
Astro with React islands:
├── Static HTML (0KB JS for non-interactive content)
├── React island (only loads when visible)
└── Total: 10-50KB of JavaScript (for interactive parts only)

The result: Astro sites score 95-100 on Google Lighthouse consistently, load in under 1 second on mobile networks, and provide a superior user experience.

Astro's Islands Architecture

What Are Islands?

Islands are interactive UI components that are hydrated independently on the page. Think of a static HTML page as an ocean—most of it is rendered at build time with zero JavaScript. Islands are the interactive components that "pop up" from this ocean, each loading its JavaScript independently.

---
// This runs at build time (server-side)
import Header from '../components/Header.astro';
import BlogPost from '../components/BlogPost.astro';
import Comments from '../components/Comments.jsx';
import ShareButtons from '../components/ShareButtons.jsx';
import TableOfContents from '../components/TableOfContents.svelte';
 
const post = await getPost(Astro.params.slug);
---
 
<!-- Static HTML: Zero JavaScript -->
<Header />
<article>
  <h1>{post.title}</h1>
  <BlogPost content={post.content} />
  
  <!-- Islands: JavaScript loads only for these -->
  <TableOfContents client:load headings={post.headings} />
  <ShareButtons client:visible url={post.url} />
  <Comments client:idle postId={post.id} />
</article>

In this example, the Header, BlogPost, and article content ship zero JavaScript. The TableOfContents, ShareButtons, and Comments are islands that load JavaScript independently using different hydration strategies.

Hydration Directives

Astro provides five hydration directives that control when island JavaScript loads:

client:load: Hydrate immediately on page load. Use for critical interactive elements that need to be interactive right away—navigation menus, search bars, and above-the-fold interactive widgets.

client:idle: Hydrate when the browser becomes idle (uses requestIdleCallback). Good for elements that don't need immediate interactivity—comment sections, analytics dashboards, and secondary interactive features.

client:visible: Hydrate when the element scrolls into the viewport (uses IntersectionObserver). Perfect for below-the-fold content—image galleries, embedded videos, and lazy-loaded interactive components.

client:media: Hydrate when a CSS media query matches. Use for responsive components that only need JavaScript on certain screen sizes—a mobile hamburger menu or a desktop-only sidebar.

client:only: Skip server-side rendering entirely and render only on the client. Use for components that depend on browser APIs (localStorage, WebGL, Web Audio) and cannot be rendered on the server.

<!-- Critical: Loads immediately -->
<SearchBar client:load />
 
<!-- Important but not urgent: Loads when browser is idle -->
<Comments client:idle />
 
<!-- Below the fold: Loads when user scrolls to it -->
<ImageGallery client:visible />
 
<!-- Mobile only: Only loads on small screens -->
<BottomNav client:media="(max-width: 768px)" />
 
<!-- Browser-only: Skips SSR entirely -->
<WebGLScene client:only="react" />

Why Islands Matter for Performance

The impact of islands on performance is dramatic. Consider a typical blog page with a header, article content, table of contents, share buttons, and comments:

Without Astro (full React SPA):

  • JavaScript bundle: 180KB gzipped
  • Time to Interactive (TTI): 4.2 seconds
  • Lighthouse Performance Score: 62

With Astro islands:

  • JavaScript bundle: 25KB gzipped (only for interactive islands)
  • Time to Interactive (TTI): 1.1 seconds
  • Lighthouse Performance Score: 98

That's a 7x reduction in JavaScript and a 4x improvement in Time to Interactive. For content-heavy sites, this translates directly to better SEO rankings, lower bounce rates, and higher conversion rates.

Content Collections

Type-Safe Content Management

Astro's Content Collections provide a type-safe way to manage content. Collections are directories of Markdown, MDX, or JSON files with a shared schema that Astro validates at build time.

// src/content/config.ts
import { defineCollection, z } from 'astro:content';
 
const blogCollection = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    date: z.string().transform(str => new Date(str)),
    description: z.string().max(160),
    tags: z.array(z.string()),
    published: z.boolean().default(true),
    author: z.string().default('Anonymous'),
    featuredImage: z.string().optional(),
  }),
});
 
export const collections = {
  blog: blogCollection,
};

With this schema, Astro validates every blog post's frontmatter at build time. If a post is missing a required field or has an invalid type, the build fails with a clear error message. This catches content errors before they reach production.

Querying Collections

Astro provides a getCollection API for querying content:

---
// Get all published blog posts, sorted by date
import { getCollection } from 'astro:content';
 
const posts = (await getCollection('blog'))
  .filter(post => post.data.published)
  .sort((a, b) => b.data.date - a.data.date);
 
// Get a specific post by slug
const post = await getEntry('blog', 'my-post-slug');
---

Content Collection Benefits

Type safety: Catch frontmatter errors at build time, not in production Auto-completion: IDE support for frontmatter fields Validation: Ensure content meets your schema requirements Performance: Astro optimizes content queries at build time Organization: Clear directory structure for different content types

View Transitions

Native Page Transitions

Astro 4 includes built-in support for the View Transitions API, enabling smooth page transitions without JavaScript frameworks. This creates app-like navigation experiences on static sites.

---
import { ViewTransitions } from 'astro:transitions';
---
 
<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <nav>
      <a href="/">Home</a>
      <a href="/blog">Blog</a>
      <a href="/about">About</a>
    </nav>
    <main>
      <slot />
    </main>
  </body>
</html>

With this single addition, all page navigations get smooth transitions. Astro handles the animation, DOM swapping, and scroll position preservation automatically.

Custom Transition Animations

You can customize transitions using the transition:name and transition:animate directives:

<!-- Element that morphs between pages -->
<img src={post.image} transition:name={`hero-${post.slug}`} />
 
<!-- Custom animation for content -->
<article transition:animate="slide">
  <h1>{post.title}</h1>
  <div set:html={post.content} />
</article>

Astro provides several built-in animations: fade, slide, and morph. You can also define custom animations using the Web Animations API:

// Custom animation definition
const customAnimation = {
  old: {
    name: 'fadeOut',
    duration: '0.3s',
    easing: 'ease-in',
    fillMode: 'forwards',
  },
  new: {
    name: 'fadeIn',
    duration: '0.3s',
    easing: 'ease-out',
    fillMode: 'backwards',
  },
};

Framework Flexibility

Use Any UI Framework

One of Astro's most powerful features is framework agnosticism. You can use React, Vue, Svelte, Solid, Preact, Lit, or Alpine.js in the same project. This is ideal for teams migrating between frameworks or projects that need different frameworks for different features.

---
// Use React for complex interactive components
import ReactChart from '../components/ReactChart.jsx';
 
// Use Svelte for lightweight UI components
import SvelteToggle from '../components/SvelteToggle.svelte';
 
// Use Vue for form handling
import VueForm from '../components/VueForm.vue';
 
// Use Solid for high-performance reactive components
import SolidCounter from '../components/SolidCounter.tsx';
---
 
<div>
  <ReactChart client:visible data={chartData} />
  <SvelteToggle client:load />
  <VueForm client:idle />
  <SolidCounter client:visible />
</div>

Framework Integration Setup

Adding framework support is straightforward:

# Add React support
npx astro add react
 
# Add multiple frameworks
npx astro add react vue svelte
 
# Or install manually
npm install @astrojs/react @astrojs/vue @astrojs/svelte
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import vue from '@astrojs/vue';
import svelte from '@astrojs/svelte';
 
export default defineConfig({
  integrations: [react(), vue(), svelte()],
});

Performance Features

Build-Time Rendering

Astro renders pages at build time by default, producing static HTML files that can be served from any CDN. This eliminates server-side rendering costs and provides the fastest possible load times.

---
// This code runs at build time, not at runtime
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
// Each post becomes a static HTML file
---
 
{posts.map(post => (
  <article>
    <h2>{post.title}</h2>
    <p>{post.excerpt}</p>
  </article>
))}

Image Optimization

Astro includes built-in image optimization that automatically converts images to modern formats (WebP, AVIF), generates responsive sizes, and lazy-loads images:

---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---
 
<!-- Automatically optimized: WebP/AVIF, responsive sizes, lazy loading -->
<Image 
  src={heroImage} 
  alt="Hero image"
  widths={[400, 800, 1200]}
  sizes="(max-width: 800px) 100vw, 800px"
/>

The Image component generates multiple sizes and formats at build time, serves the optimal version based on the user's device and browser, and adds loading="lazy" by default.

CSS Optimization

Astro scopes CSS by default, eliminating unused styles and preventing CSS conflicts:

<style>
  /* This CSS is scoped to this component only */
  .title {
    font-size: 2rem;
    color: var(--text-primary);
  }
</style>
 
<h1 class="title">Scoped styles</h1>

Astro also supports Tailwind CSS, Sass, and CSS Modules out of the box. Unused CSS is automatically purged at build time.

Server-Side Rendering

When You Need SSR

While Astro defaults to static site generation, some features require server-side rendering:

  • User authentication: Protected pages that show different content based on login state
  • Dynamic data: Pages that show real-time data (dashboards, live feeds)
  • Form handling: Server-side form processing and validation
  • Personalization: Content that changes based on user preferences
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
 
export default defineConfig({
  output: 'hybrid', // Static by default, SSR where needed
  adapter: node(),
});
---
// Mark specific pages as server-rendered
export const prerender = false;
 
// This runs on every request
const user = await getUser(Astro.request);
if (!user) {
  return Astro.redirect('/login');
}
---
 
<h1>Welcome, {user.name}</h1>

Hybrid Rendering

Astro's hybrid rendering mode lets you mix static and server-rendered pages in the same project. Most pages are built as static HTML for maximum performance, while specific pages use SSR for dynamic features.

This is ideal for sites that are mostly content but have a few dynamic features—like a blog with user comments, a documentation site with search, or a marketing site with a contact form.

Real-World Use Cases

Blog / Documentation Sites

Astro excels at content-heavy sites. Features like Content Collections, MDX support, automatic RSS feeds, and sitemap generation make it ideal for blogs and documentation portals.

---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';
 
export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}
 
const { post } = Astro.props;
const { Content } = await post.render();
---
 
<article>
  <h1>{post.data.title}</h1>
  <time>{post.data.date.toLocaleDateString()}</time>
  <Content />
</article>

Marketing / Landing Pages

Marketing sites need fast load times and excellent SEO. Astro's zero-JavaScript default and built-in image optimization deliver both. Add interactive elements (forms, carousels, animations) as islands that load only when needed.

E-Commerce Product Pages

Product pages benefit from Astro's approach: static product information loads instantly, while interactive elements (add-to-cart, image zoom, reviews) are islands that load on demand. This provides a fast initial experience while maintaining full interactivity.

Astro DB and Server Islands

Built-in Database Layer

Astro 4 introduced Astro DB, a built-in database layer powered by LibSQL (a fork of SQLite). This eliminates the need for external database services for simple use cases like analytics, form submissions, and user preferences:

// db/config.ts
import { defineDb, defineTable, column } from 'astro:db';
 
const Comments = defineTable({
  columns: {
    id: column.number({ primaryKey: true }),
    postId: column.text(),
    author: column.text(),
    body: column.text(),
    createdAt: column.date({ default: new Date() }),
  },
});
 
export default defineDb({ tables: { Comments } });
---
// src/pages/api/comments.ts
import { db, Comments, eq } from 'astro:db';
 
export async function POST({ request }) {
  const { postId, author, body } = await request.json();
  await db.insert(Comments).values({ postId, author, body });
  return new Response('OK');
}

Astro DB works in both static and server modes. In static mode, you can seed data at build time. In server mode, it provides a full read-write database. This is perfect for blogs with comments, documentation sites with feedback forms, or small applications that don't need a full database server.

Server Islands

Server Islands solve the problem of mixing static and dynamic content on the same page. Traditional SSR renders the entire page on every request, even if only a small portion is dynamic. Server Islands let you mark specific components as server-rendered while the rest of the page is static:

---
// Most of the page is static HTML
import Header from '../components/Header.astro';
import Article from '../components/Article.astro';
import Footer from '../components/Footer.astro';
---
 
<Header />
<Article content={post.content} />
 
<!-- This island is server-rendered on every request -->
<Fragment slot="server-island">
  <UserGreeting server:defer>
    <p slot="fallback">Loading...</p>
  </UserGreeting>
</Fragment>
 
<Footer />

The UserGreeting component is rendered on the server for each request, while the rest of the page is served from the CDN cache. This gives you personalized content without the performance cost of full SSR. Use Server Islands for user-specific content (shopping carts, notifications, personalized recommendations) on otherwise static pages.

Astro Middleware

Astro middleware runs before a page is rendered, allowing you to modify the request, check authentication, set headers, or redirect users:

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
 
export const onRequest = defineMiddleware(async (context, next) => {
  // Check authentication for protected routes
  if (context.url.pathname.startsWith('/dashboard')) {
    const session = context.cookies.get('session');
    if (!session) {
      return context.redirect('/login');
    }
  }
 
  // Set security headers
  const response = await next();
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-Content-Type-Options', 'nosniff');
  
  return response;
});

Middleware is particularly useful for:

  • Authentication: Protect routes that require login
  • Localization: Detect user language and serve localized content
  • A/B testing: Route users to different page variants
  • Analytics: Track page views and user behavior
  • Security headers: Add CSP, HSTS, and other security headers

Migration Guide

From Next.js

Migrating from Next.js to Astro is straightforward for content sites:

  1. Move pages from pages/ to src/pages/
  2. Convert React components to .astro components (or keep them as islands)
  3. Replace getStaticProps with top-level await in frontmatter
  4. Replace getStaticPaths with Astro's getStaticPaths
  5. Move styles from CSS-in-JS to <style> blocks or global CSS
  6. Replace Next.js Image with Astro's Image component
  7. Replace next/head with Astro's built-in <head> handling

From Gatsby

Gatsby sites can migrate to Astro with minimal changes:

  1. Move content to Content Collections
  2. Replace GraphQL queries with getCollection calls
  3. Convert components to .astro or keep as React islands
  4. Replace Gatsby plugins with Astro integrations
  5. Replace Gatsby's gatsby-image with Astro's image optimization

From 11ty / Hugo

Static site generators like 11ty and Hugo share Astro's philosophy of shipping minimal JavaScript. Migration is straightforward:

  1. Move templates to Astro components
  2. Convert shortcodes to Astro components or functions
  3. Move content to Content Collections
  4. Replace build scripts with Astro's built-in features

Ecosystem and Community

Astro's ecosystem has grown rapidly. The integrations marketplace includes official and community-built packages for authentication, analytics, CMS connections, and deployment targets. Popular integrations include:

  • @astrojs/tailwind: Tailwind CSS support with zero configuration
  • @astrojs/mdx: MDX support for interactive content
  • @astrojs/sitemap: Automatic sitemap generation
  • @astrojs/partytown: Offload third-party scripts to web workers
  • astro-icon: Optimized icon component with 100+ icon sets
  • astro-seo: SEO meta tags and Open Graph configuration

The Astro community is active and welcoming. The Discord server has over 30,000 members, and the framework is backed by a growing number of contributors. The Astro team releases updates frequently, with a focus on backward compatibility and incremental improvements.

SEO and Accessibility

Built-In SEO Features

Astro provides comprehensive SEO support out of the box. Because pages are rendered as static HTML, search engine crawlers can index every page without executing JavaScript. This is a significant advantage over JavaScript-heavy frameworks where crawlers may miss dynamically rendered content.

The framework includes automatic sitemap generation, RSS feed support for blogs, and proper meta tag handling. You can set page titles, descriptions, and Open Graph tags directly in your Astro components or frontmatter. The @astrojs/sitemap integration generates a complete sitemap at build time, ensuring search engines discover all your pages.

Structured data for rich snippets is straightforward to implement. Because Astro outputs clean HTML, adding JSON-LD schema markup is a matter of including a script tag in your page head. There is no hydration delay or JavaScript execution required before the structured data is available to crawlers.

Accessibility by Default

Astro's approach to shipping minimal JavaScript inherently improves accessibility. Screen readers and assistive technologies work best with semantic HTML, and that is exactly what Astro produces. The framework encourages using native HTML elements, proper heading hierarchies, and ARIA attributes where needed.

The islands architecture also benefits accessibility. Because interactive components are isolated, each island can be tested independently for accessibility compliance. This modular approach makes it easier to ensure that every interactive element meets WCAG guidelines.

Deployment Options

Static Hosting

Astro's default output is static HTML, which means you can deploy to virtually any hosting provider. The build output is a directory of HTML, CSS, JavaScript, and asset files that can be served from a CDN.

Popular deployment targets include Vercel, Netlify, Cloudflare Pages, GitHub Pages, and AWS S3 with CloudFront. Each platform has an Astro adapter that handles the deployment configuration automatically. For static sites, the deployment process is typically as simple as connecting your Git repository and pushing changes.

Edge and Server Deployment

For sites using Astro's hybrid or server rendering modes, deployment requires a server runtime. Astro supports Node.js, Deno, Cloudflare Workers, Vercel Edge Functions, and Netlify Edge Functions. The choice depends on your performance requirements and existing infrastructure.

Edge deployment is particularly interesting for Astro sites that mix static and dynamic content. Static pages are served from the CDN with sub-50ms latency, while dynamic pages run at the edge close to users. This hybrid approach provides the best of both worlds: static performance for content and dynamic capability for interactive features.

Community and Ecosystem

Growing Ecosystem

Astro's ecosystem has grown rapidly since its initial release. The integrations directory includes official and community-built adapters for React, Vue, Svelte, Solid, Preact, Lit, Alpine, and HTMX. There are also integrations for Tailwind CSS, MDX, partytown for third-party script optimization, and prefetch for faster navigation.

The theme and template ecosystem provides starting points for common use cases. Whether you are building a blog, a documentation site, a portfolio, or a marketing landing page, there are production-ready templates that demonstrate best practices and provide a solid foundation.

Active Community

The Astro community is one of the most welcoming in the web development space. The Discord server has tens of thousands of members who help each other with questions, share their projects, and contribute to the framework. The core team is responsive and actively engages with community feedback.

The framework also has excellent documentation. The Astro docs include interactive tutorials, detailed guides for every feature, and a comprehensive API reference. For developers coming from other frameworks, the migration guides provide step-by-step instructions for moving existing projects to Astro.

Comparison with Other Frameworks

FeatureAstroNext.jsGatsby11ty
Default JS shipped0KB45KB+45KB+0KB
Framework supportAnyReactReactAny
Build speedFastMediumSlowFast
SSR supportYes (hybrid)YesYesNo
Content collectionsBuilt-inManualGraphQLManual
View transitionsBuilt-inManualManualManual
Learning curveLowMediumHighLow

Conclusion

Astro 4 represents a fundamental shift in how we think about web development. Instead of defaulting to JavaScript-heavy frameworks for every website, Astro lets you choose the right level of interactivity for each component. Static content stays static. Interactive components load JavaScript only when needed.

The result is websites that are faster, more accessible, and better optimized for search engines—without sacrificing developer experience or the ability to use modern UI frameworks.

Key takeaways:

  1. Ship zero JavaScript by default — Only interactive islands load JavaScript, reducing bundle sizes by 80-95%
  2. Use any framework — React, Vue, Svelte, Solid, and more can coexist in the same project
  3. Content Collections provide type-safe content — Schema validation catches errors at build time
  4. View Transitions create app-like experiences — Smooth page transitions without JavaScript frameworks
  5. Hybrid rendering gives you flexibility — Mix static and server-rendered pages as needed

If you're building a content-focused website, Astro should be at the top of your evaluation list. The performance benefits alone justify the switch, and the developer experience makes it a pleasure to work with. Start with the official Astro tutorial, build a small project, and experience the difference that shipping zero JavaScript makes.