Astro - Modern Site Generator with Islands Architecture
Astro Modern Site
frontendAstro - The Modern Static Site Generator
Astro is a next-generation web framework that fundamentally changes the approach to building websites. Instead of sending kilobytes of JavaScript to the browser, Astro generates clean HTML and CSS, adding JavaScript only where it is truly needed. This "zero JS by default" philosophy makes Astro-built sites among the fastest solutions available on the market.
In this article, we will explore the key concepts of Astro - from Islands architecture, through .astro components, Content Collections, to SSR mode and performance comparisons with Next.js and Gatsby.
Islands Architecture - A Revolution in UI Building#
Islands architecture is Astro's key innovation. Traditional SPA frameworks treat the entire page as one large JavaScript application - even when most of the content is static. Astro reverses this model entirely.
How Do Islands Work?#
In Astro, every page is static HTML by default. Interactive elements - buttons, forms, carousels - are treated as independent JavaScript "islands" embedded in a sea of static HTML:
---
// src/pages/index.astro
import Header from '../components/Header.astro';
import HeroSection from '../components/HeroSection.astro';
import ProductCarousel from '../components/ProductCarousel.tsx';
import Newsletter from '../components/Newsletter.vue';
import Footer from '../components/Footer.astro';
---
<html lang="en">
<body>
<!-- Static HTML - zero JavaScript -->
<Header />
<HeroSection />
<!-- Interactive "island" - React, loaded when visible -->
<ProductCarousel client:visible />
<!-- Interactive "island" - Vue, loaded immediately -->
<Newsletter client:load />
<!-- Static HTML -->
<Footer />
</body>
</html>
Client Directives#
Astro provides precise control over when JavaScript gets loaded:
client:load- loads the component immediately on page loadclient:idle- loads when the browser is idle (requestIdleCallback)client:visible- loads when the component enters the viewport (Intersection Observer)client:media- loads when a media query condition is metclient:only- renders the component exclusively on the client side
---
import HeavyChart from '../components/HeavyChart.tsx';
import MobileMenu from '../components/MobileMenu.svelte';
import ChatWidget from '../components/ChatWidget.tsx';
---
<!-- Chart loaded only when the user scrolls to it -->
<HeavyChart client:visible />
<!-- Mobile menu loaded only on small screens -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- Chat loaded when the browser is idle -->
<ChatWidget client:idle />
This granular control means the user only downloads JavaScript when they need it - and only as much as they need.
Zero JavaScript by Default#
Unlike Next.js or Gatsby, where every page includes the React runtime (approximately 40-80 KB gzipped), Astro sends zero JavaScript by default. A page built entirely from .astro components generates clean HTML and CSS.
---
// src/components/BlogCard.astro
// This component renders to pure HTML - zero JS
interface Props {
title: string;
excerpt: string;
date: string;
slug: string;
}
const { title, excerpt, date, slug } = Astro.props;
const formattedDate = new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
---
<article class="blog-card">
<time datetime={date}>{formattedDate}</time>
<h3><a href={`/blog/${slug}`}>{title}</a></h3>
<p>{excerpt}</p>
</article>
<style>
.blog-card {
padding: 1.5rem;
border-radius: 8px;
background: var(--surface);
transition: transform 0.2s ease;
}
.blog-card:hover {
transform: translateY(-2px);
}
</style>
The entire component above compiles to static HTML and scoped CSS. The browser does not need to download or parse any JavaScript whatsoever.
Framework Agnostic - Use React, Vue, Svelte, or All of Them#
One of Astro's most unique features is the ability to mix components from different frameworks in a single project. You can use a React component alongside a Vue component and a Svelte component - Astro handles the rest.
Setting Up Integrations#
// 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()],
});
Practical Example#
---
// src/pages/dashboard.astro
// Each component uses the framework it was built with
import ReactDataGrid from '../components/DataGrid.tsx';
import VueFormWizard from '../components/FormWizard.vue';
import SvelteNotifications from '../components/Notifications.svelte';
---
<main>
<h1>Admin Dashboard</h1>
<!-- React - excellent for complex data tables -->
<ReactDataGrid client:load data={dashboardData} />
<!-- Vue - great for multi-step form wizards -->
<VueFormWizard client:visible />
<!-- Svelte - lightweight and fast for notifications -->
<SvelteNotifications client:idle />
</main>
This approach is particularly valuable during migrations - you can gradually move components from one framework to another without rewriting the entire application at once.
Astro Components - The Power of Simplicity#
.astro components are Astro's native format, combining server-side logic with an HTML template in a single file. The syntax resembles a combination of JSX and frontmatter:
---
// src/components/Navigation.astro
// The "fence" block --- executes on the server
interface Props {
currentPath: string;
}
const { currentPath } = Astro.props;
const navItems = [
{ href: '/', label: 'Home' },
{ href: '/services', label: 'Services' },
{ href: '/portfolio', label: 'Portfolio' },
{ href: '/blog', label: 'Blog' },
{ href: '/contact', label: 'Contact' },
];
// You can use top-level await
const response = await fetch('https://api.example.com/announcements');
const announcements = await response.json();
---
<nav class="main-nav">
<ul>
{navItems.map((item) => (
<li>
<a
href={item.href}
class:list={[
'nav-link',
{ active: currentPath === item.href }
]}
>
{item.label}
</a>
</li>
))}
</ul>
{announcements.length > 0 && (
<div class="announcement-bar">
{announcements[0].message}
</div>
)}
</nav>
<style>
.nav-link {
text-decoration: none;
color: var(--text);
padding: 0.5rem 1rem;
}
.nav-link.active {
color: var(--primary);
font-weight: 600;
}
</style>
Key characteristics of .astro components:
- Top-level await - you can fetch data directly within the component
- Scoped styles - styles are isolated to the component by default
- No reactivity - they render once on the server, making them extremely fast
- TypeScript out-of-the-box - full support without additional configuration
Content Collections - Type-Safe Content Management#
Content Collections is Astro's built-in content management system. It allows you to organize blog posts, documentation, or products with full type validation.
Defining Collections#
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string().max(160),
date: z.coerce.date(),
updated: z.coerce.date().optional(),
category: z.enum(['frontend', 'backend', 'devops', 'mobile']),
tags: z.array(z.string()),
author: z.string().default('MDS Software Solutions Group'),
cover: z.string(),
draft: z.boolean().default(false),
locale: z.enum(['pl', 'en', 'de']),
}),
});
const projectsCollection = defineCollection({
type: 'content',
schema: z.object({
name: z.string(),
client: z.string(),
technologies: z.array(z.string()),
year: z.number(),
featured: z.boolean().default(false),
}),
});
export const collections = {
blog: blogCollection,
projects: projectsCollection,
};
Fetching and Displaying Content#
---
// src/pages/blog/[...slug].astro
import { getCollection, getEntry } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => {
return data.draft !== true && data.locale === 'en';
});
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content, headings } = await post.render();
---
<BlogLayout frontmatter={post.data}>
<nav class="toc">
<h2>Table of Contents</h2>
<ul>
{headings
.filter((h) => h.depth <= 3)
.map((h) => (
<li style={`margin-left: ${(h.depth - 2) * 1}rem`}>
<a href={`#${h.slug}`}>{h.text}</a>
</li>
))}
</ul>
</nav>
<Content />
</BlogLayout>
Content Collections automatically validate frontmatter, generate TypeScript types, and provide full IntelliSense in your code editor.
File-Based Routing#
Astro uses a file-based routing system within the src/pages/ directory. It is intuitive and requires no configuration:
src/pages/
index.astro -> /
about.astro -> /about
blog/
index.astro -> /blog
[slug].astro -> /blog/:slug (dynamic)
[...slug].astro -> /blog/* (catch-all)
api/
search.ts -> /api/search (API endpoint)
Dynamic Routes with Parameters#
---
// src/pages/category/[category].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
const categories = [...new Set(posts.map((p) => p.data.category))];
return categories.map((category) => ({
params: { category },
props: {
posts: posts.filter((p) => p.data.category === category),
},
}));
}
const { category } = Astro.params;
const { posts } = Astro.props;
---
<h1>Category: {category}</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
</li>
))}
</ul>
API Endpoints#
// src/pages/api/search.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
export const GET: APIRoute = async ({ url }) => {
const query = url.searchParams.get('q')?.toLowerCase() || '';
const posts = await getCollection('blog');
const results = posts
.filter((post) =>
post.data.title.toLowerCase().includes(query) ||
post.data.description.toLowerCase().includes(query)
)
.map((post) => ({
title: post.data.title,
slug: post.slug,
description: post.data.description,
}));
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' },
});
};
View Transitions API - Smooth Page Transitions#
Astro was one of the first frameworks to integrate the native View Transitions API, enabling smooth transition animations between pages without requiring a SPA:
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html lang="en">
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
Custom Animations#
---
// src/components/BlogCard.astro
import { fade, slide } from 'astro:transitions';
---
<article transition:animate={slide({ duration: '0.3s' })}>
<img
src={cover}
alt={title}
transition:name={`blog-cover-${slug}`}
/>
<h3 transition:name={`blog-title-${slug}`}>
{title}
</h3>
</article>
With transition:name, Astro automatically animates elements sharing the same identifier across pages - for example, a post thumbnail on the list smoothly transitions into a large image on the post page.
SSR Mode - Server-Side Rendering#
Astro also supports dynamic server-side rendering (SSR), enabling you to build full web applications:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server', // or 'hybrid' for a mixed approach
adapter: node({
mode: 'standalone',
}),
});
Hybrid Mode#
The hybrid mode pre-renders pages by default, with the option to mark specific ones as dynamic:
---
// src/pages/dashboard.astro
// This page will be rendered dynamically
export const prerender = false;
import { getSession } from '../lib/auth';
const session = await getSession(Astro.request);
if (!session) {
return Astro.redirect('/login');
}
---
<h1>Welcome, {session.user.name}!</h1>
Astro provides adapters for various deployment platforms: Node.js, Vercel, Netlify, Cloudflare Workers, Deno, and more.
Integrations - Tailwind, MDX, and More#
The Astro ecosystem offers a rich set of official and community integrations:
# Install integrations with a single command
npx astro add tailwind
npx astro add mdx
npx astro add sitemap
npx astro add image
Configuration with Tailwind CSS#
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com',
integrations: [
tailwind({
applyBaseStyles: false,
}),
mdx(),
sitemap({
filter: (page) => !page.includes('/admin/'),
}),
],
});
MDX with Custom Components#
---
title: How to Optimize Website Performance
---
import Callout from '../../components/Callout.astro';
import CodePlayground from '../../components/CodePlayground.tsx';
# How to Optimize Website Performance
<Callout type="tip">
Astro automatically optimizes images - no additional tooling required.
</Callout>
## Live Example
<CodePlayground client:visible code={`console.log('Hello Astro!')`} />
Image Optimization#
Astro offers built-in image optimization through the <Image /> component:
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---
<!-- Automatic optimization: WebP/AVIF conversion, responsive sizes -->
<Image
src={heroImage}
alt="Image description"
widths={[400, 800, 1200]}
sizes="(max-width: 800px) 100vw, 800px"
format="webp"
quality={80}
/>
<!-- Remote images with specified dimensions -->
<Image
src="https://example.com/photo.jpg"
alt="Remote photo"
width={800}
height={600}
format="avif"
/>
Astro automatically generates appropriate sizes, converts to modern formats, and adds width/height attributes to prevent layout shift (CLS).
Astro DB - Built-in Database#
Astro DB is a database solution built on libSQL (a SQLite fork), designed specifically to work with Astro:
// db/config.ts
import { defineDb, defineTable, column } from 'astro:db';
const Comment = defineTable({
columns: {
id: column.number({ primaryKey: true }),
postSlug: column.text(),
author: column.text(),
content: column.text(),
createdAt: column.date({ default: new Date() }),
approved: column.boolean({ default: false }),
},
});
export default defineDb({
tables: { Comment },
});
---
// src/pages/blog/[slug].astro
import { db, Comment, eq } from 'astro:db';
const comments = await db
.select()
.from(Comment)
.where(eq(Comment.postSlug, slug))
.where(eq(Comment.approved, true))
.orderBy(Comment.createdAt);
---
<section class="comments">
{comments.map((comment) => (
<div class="comment">
<strong>{comment.author}</strong>
<p>{comment.content}</p>
<time>{comment.createdAt.toLocaleDateString('en-US')}</time>
</div>
))}
</section>
Performance Comparison with Next.js and Gatsby#
Astro consistently wins performance benchmarks for content-driven sites:
JavaScript Sent to the Client#
| Framework | Homepage (typical) | Blog post | |-----------|-------------------|-----------| | Astro | 0 KB | 0 KB | | Next.js | ~85 KB | ~85 KB | | Gatsby | ~70 KB | ~70 KB |
Core Web Vitals Metrics (typical blog page)#
| Metric | Astro | Next.js | Gatsby | |--------|-------|---------|--------| | LCP | ~0.8s | ~1.5s | ~1.8s | | FID | ~5ms | ~30ms | ~25ms | | CLS | 0 | ~0.05 | ~0.08 | | TTFB | ~50ms | ~80ms | ~60ms |
The difference is especially noticeable on mobile devices with slow connections - no JavaScript to download and parse means instantaneous page loading.
Build Time (1,000 Markdown pages)#
| Framework | Build Time | |-----------|-----------| | Astro | ~15s | | Next.js | ~45s | | Gatsby | ~90s |
When to Choose Astro#
Astro is ideal for:#
- Blogs and content-driven sites - maximum performance for static content
- Corporate websites and portfolios - fast loading, excellent SEO
- Technical documentation - Content Collections and MDX
- Landing pages - minimal JS, instant loading
- E-commerce storefronts - page speed directly impacts conversions
- Multilingual websites - built-in i18n support
When to consider alternatives:#
- Complex SPA applications (e.g., real-time dashboards) - Next.js or SvelteKit
- Applications requiring constant interactivity - React/Vue SPA
- Full-stack with heavy backend - Next.js, Remix, or NestJS
Quick Start with Astro#
# Create a new project
npm create astro@latest my-astro-site
# Navigate to the project directory
cd my-astro-site
# Add integrations
npx astro add tailwind
npx astro add mdx
# Start the development server
npm run dev
Conclusion#
Astro is a framework that changes the rules of website development. With its Islands architecture, "zero JavaScript by default" approach, and multi-framework support, it offers a unique combination of performance and flexibility.
If you are building a site where content is paramount - a blog, corporate website, documentation, or landing page - Astro should be your first choice. The Core Web Vitals results speak for themselves.
Planning to build a modern website focused on performance and SEO? The MDS Software Solutions Group team specializes in building high-performance web applications using Astro, Next.js, and other modern technologies. Contact us - we will help you choose the best solution and build a site that impresses with its speed.
Team of programming experts specializing in modern web technologies.