Wer 2026 eine performante Website baut, kommt an Astro kaum vorbei. Das Framework hat sich von einem Static-Site-Generator zu einer vollständigen Web-Plattform entwickelt: Server-Side Rendering, Astro DB, View Transitions und die berühmte Islands Architecture sind heute Production-ready. Claude Code kennt den gesamten Astro-5-Stack und generiert korrekte .astro-Komponenten, typsichere Content Collections und optimierte Deployment-Konfigurationen — ohne dass du jede API-Referenz auswendig lernen musst.
Was du lernst: Astro Grundkonzept & Zero-JS-Ansatz, Islands Architecture mit allen client:-Direktiven, Content Collections mit Zod-Validierung, SSR-Adapter, Astro DB mit Drizzle ORM und Performance-Optimierung mit View Transitions.
1. Astro Grundkonzept — Zero-JS by Default
Astro verfolgt einen radikal anderen Ansatz als Next.js oder Remix: Standardmäßig wird kein JavaScript an den Browser ausgeliefert. Jede .astro-Datei besteht aus zwei Teilen: dem Frontmatter (zwischen ----Trennlinien) und dem Template. Das Frontmatter läuft ausschließlich auf dem Server zur Build-Zeit — nie im Browser.
ASTRO Grundstruktur einer .astro-Komponente
Claude Code generiert vollständige Astro-Komponenten mit korrektem Frontmatter-Pattern:
---
// Frontmatter — läuft NUR auf dem Server/Build
import Layout from '../layouts/Layout.astro';
import Hero from '../components/Hero.astro';
import { getCollection } from 'astro:content';
// Daten werden zur Build-Zeit abgerufen
const posts = await getCollection('blog');
const latestPosts = posts.slice(0, 3);
// Props-Interface (TypeScript nativ unterstützt)
interface Props {
title?: string;
}
const { title = 'Startseite' } = Astro.props;
---
<!-- Template — rendert zu statischem HTML -->
<Layout title={title}>
<Hero />
<section class="latest-posts">
<h2>Neueste Artikel</h2>
<ul>
{latestPosts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
<time>{post.data.pubDate.toLocaleDateString('de-DE')}</time>
</li>
))}
</ul>
</section>
</Layout>
LAYOUT Layout-Komponente mit Slot
Layouts nutzen <slot /> als Platzhalter für Seiteninhalte — Claude Code erstellt automatisch wiederverwendbare Layouts:
---
interface Props {
title: string;
description?: string;
}
const { title, description = 'Meine Astro Website' } = Astro.props;
---
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>
<slot /> <!-- Seiteninhalt wird hier eingefügt -->
</main>
<footer>
<p>© 2026 Meine Website</p>
</footer>
</body>
</html>
<!-- Scoped Styles — gelten nur für diese Komponente -->
<style>
main {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
</style>
Claude Code Vorteil: Claude Code versteht den Unterschied zwischen Frontmatter (Server) und Template (HTML-Output) intuitiv. Anfragen wie "erstelle eine Astro-Seite mit Blog-Listing" generieren sofort korrekten, typsicheren Code ohne manuelle Korrekturen.
2. Islands Architecture — Selektive Interaktivität
Das Herzstück von Astro ist die Islands Architecture: Interaktive UI-Komponenten (React, Vue, Svelte, Solid) werden als "Inseln" in ansonsten statisches HTML eingebettet. Jede Insel lädt ihr JavaScript nur dann, wenn es wirklich gebraucht wird — gesteuert durch client:-Direktiven.
ISLAND client:load — Sofort hydratisieren
Für Komponenten, die sofort beim Seitenaufruf interaktiv sein müssen (z.B. Navigation, Login-Buttons):
---
import Navbar from '../components/Navbar.jsx';
import SearchBar from '../components/SearchBar.tsx';
import LazyChart from '../components/Chart.vue';
import Comments from '../components/Comments.svelte';
---
<!-- client:load: JavaScript sofort laden und hydratisieren -->
<Navbar client:load />
<!-- client:idle: Laden wenn Browser idle (requestIdleCallback) -->
<!-- Ideal für nicht-kritische Interaktivität -->
<SearchBar client:idle />
<!-- client:visible: Laden wenn Element im Viewport (IntersectionObserver) -->
<!-- Perfekt für below-the-fold Inhalte -->
<LazyChart client:visible />
<!-- client:visible mit threshold: 0.5 = 50% sichtbar -->
<Comments client:visible={{rootMargin: "200px"}} />
<!-- client:only: NUR im Browser rendern (kein SSR) -->
<!-- Für Komponenten die window/document benötigen -->
import Map from '../components/LeafletMap.jsx';
<Map client:only="react" center={[52.52, 13.4]} zoom={12} />
<!-- client:media: Nur laden wenn Media Query matched -->
import MobileMenu from '../components/MobileMenu.tsx';
<MobileMenu client:media="(max-width: 768px)" />
ISLAND React-Komponente als Insel
Framework-Komponenten funktionieren wie gewohnt — Astro übernimmt das Hydration-Management:
// src/components/Counter.tsx — React-Komponente
import { useState } from 'react';
interface CounterProps {
initialCount?: number;
label: string;
}
export default function Counter({ initialCount = 0, label }: CounterProps) {
const [count, setCount] = useState(initialCount);
return (
<div className="counter-island">
<p>{label}: <strong>{count}</strong></p>
<button onClick={() => setCount(c => c - 1)}>−</button>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
---
import Counter from '../components/Counter.tsx';
---
<!-- Props werden sicher an die Insel übergeben -->
<Counter
client:visible
initialCount={5}
label="Klicks heute"
/>
Direktiven-Wahl: Falsche Direktiven kosten Performance. client:load für alles zu verwenden erhöht den JavaScript-Bundle unnötig. Claude Code wählt automatisch die optimale Direktive basierend auf dem Verwendungszweck der Komponente.
3. Content Collections — Typsicheres Content Management
Content Collections sind Astros eingebautes CMS-System. Markdown-, MDX- und JSON-Dateien werden automatisch mit Zod-Schemas validiert — du bekommst TypeScript-Typen für jeden Frontmatter-Wert, vollständige Autovervollständigung und Build-Zeit-Fehler bei ungültigen Daten.
CONTENT Collection Schema definieren
Das Schema wird in src/content/config.ts definiert und bei jedem Build überprüft:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
// Blog-Collection mit vollständiger Zod-Validierung
const blogCollection = defineCollection({
type: 'content', // 'content' für MD/MDX, 'data' für JSON/YAML
schema: z.object({
title: z.string().min(5).max(100),
description: z.string().min(10),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
author: z.string().default('Redaktion'),
image: z.object({
url: z.string(),
alt: z.string(),
}).optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
readingTime: z.number().positive().optional(),
category: z.enum(['tutorial', 'news', 'case-study', 'opinion']),
}),
});
// Authors als Data-Collection (JSON/YAML)
const authorsCollection = defineCollection({
type: 'data',
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string().url(),
social: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
}),
}),
});
export const collections = {
blog: blogCollection,
authors: authorsCollection,
};
CONTENT getCollection() und getEntry()
Collections abfragen, filtern und sortieren — vollständig typsicher:
---
import { getCollection, getEntry } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
// Alle publizierten Posts abrufen (draft:false filtern)
const allPosts = await getCollection('blog', ({ data }) => {
return data.draft === false;
});
// Nach Datum sortieren (neueste zuerst)
const sortedPosts = allPosts.sort(
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime()
);
// Nach Tags filtern
const astroPosts = allPosts.filter(
post => post.data.tags.includes('astro')
);
// Einzelnen Author abrufen
const author = await getEntry('authors', 'max-mustermann');
---
<Layout title="Blog">
<section>
{sortedPosts.map(post => (
<article>
<h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
<p>{post.data.description}</p>
<div class="meta">
<span>{post.data.author}</span>
<time>{post.data.pubDate.toLocaleDateString('de-DE')}</time>
{post.data.tags.map(tag => <span class="tag">{tag}</span>)}
</div>
</article>
))}
</section>
</Layout>
CONTENT Dynamische Routen mit getStaticPaths()
Individuelle Blog-Post-Seiten werden automatisch für alle Collection-Einträge generiert:
---
import { getCollection, getEntry } from 'astro:content';
// Pflicht für statische Builds: alle Pfade vorausberechnen
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(); // Markdown → HTML
// Verwandten Author abrufen (type-safe!)
const author = await getEntry('authors', post.data.author);
---
import Layout from '../../layouts/Layout.astro';
<Layout
title={post.data.title}
description={post.data.description}
>
<article>
<h1>{post.data.title}</h1>
<div class="author">
<img src={author?.data.avatar} alt={author?.data.name} />
<span>{author?.data.name}</span>
</div>
<Content /> <!-- Markdown wird hier gerendert -->
</article>
</Layout>
Schema-Validierung bei Build: Fehlt ein Pflichtfeld oder hat ein Wert den falschen Typ, bricht der Build mit einem klaren Fehler ab. Claude Code fügt beim Erstellen von Markdown-Dateien automatisch alle Pflichtfelder aus dem Schema ein.
4. Server-Side Rendering — Astro als vollständiger Backend
Ab Astro 4 ist SSR stabil und Production-ready. Mit dem Umstieg auf output: 'server' wird Astro zur vollständigen Full-Stack-Lösung: dynamische Seiten, API Routes, Middleware, Authentifizierung und Datenbankzugriff — alles in einem Framework.
SSR astro.config.mjs — Output und Adapter
Der Adapter bestimmt die Deployment-Plattform. Claude Code generiert die passende Konfiguration:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node'; // VPS / Docker
import vercel from '@astrojs/vercel'; // Vercel Serverless
import cloudflare from '@astrojs/cloudflare'; // Cloudflare Pages/Workers
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
export default defineConfig({
output: 'server', // 'static' | 'hybrid' | 'server'
adapter: node({ mode: 'standalone' }), // Node.js Adapter
integrations: [
react(), // React Islands aktivieren
tailwind(), // Tailwind CSS
],
// Hybrid-Mode: Seiten können individuell prerendern
// output: 'hybrid' → Standard: SSR, opt-in: static
// Vercel Adapter Alternative:
// adapter: vercel({ imageService: true, webAnalytics: { enabled: true } }),
vite: {
envPrefix: 'PUBLIC_', // Nur PUBLIC_* Vars im Client
},
});
SSR API Routes — REST Endpoints in Astro
API Routes werden als .ts-Dateien in src/pages/api/ erstellt:
// src/pages/api/newsletter.ts — POST Endpoint
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request, locals }) => {
const data = await request.json();
// Input-Validierung
if (!data.email || !data.email.includes('@')) {
return new Response(
JSON.stringify({ error: 'Ungültige E-Mail-Adresse' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
// Datenbankzugriff via Locals (Middleware setzt runtime-Context)
const { db } = locals.runtime.env;
try {
await db.prepare('INSERT INTO subscribers (email) VALUES (?)')
.bind(data.email)
.run();
return new Response(
JSON.stringify({ success: true, message: 'Erfolgreich angemeldet!' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
} catch (error) {
return new Response(
JSON.stringify({ error: 'Datenbankfehler' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
};
// GET Endpoint für Subscriber-Count
export const GET: APIRoute = async ({ request }) => {
const count = 42; // aus DB lesen
return new Response(JSON.stringify({ count }));
};
SSR Middleware — Auth und Context
Astro Middleware (src/middleware.ts) läuft vor jeder Request-Verarbeitung:
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
const { request, locals, cookies, redirect } = context;
// Session aus Cookie lesen
const sessionToken = cookies.get('session')?.value;
if (sessionToken) {
// User in locals speichern → in Seiten via Astro.locals verfügbar
locals.user = await validateSession(sessionToken);
}
// Protected Routes absichern
const isProtected = request.url.includes('/dashboard');
if (isProtected && !locals.user) {
return redirect('/login?redirect=' + request.url);
}
return next();
});
5. Astro DB + Drizzle — Eingebautes ORM
Astro DB ist eine in Astro integrierte Datenbankschicht basierend auf Drizzle ORM und libSQL (SQLite-kompatibel). Lokal läuft die Datenbank als SQLite-Datei, in Production verbindet sich Astro DB automatisch mit Turso oder einem kompatiblen libSQL-Server.
DB Datenbankschema definieren
Das Schema wird in db/config.ts mit der Astro DB API definiert:
// db/config.ts — Astro DB Schema
import { defineDb, defineTable, column, NOW } from 'astro:db';
// Tabellen definieren
const Author = defineTable({
columns: {
id: column.number({ primaryKey: true }),
name: column.text(),
email: column.text({ unique: true }),
bio: column.text({ optional: true }),
createdAt: column.date({ default: NOW }),
},
});
const Post = defineTable({
columns: {
id: column.number({ primaryKey: true }),
slug: column.text({ unique: true }),
title: column.text(),
body: column.text(),
authorId: column.number({ references: () => Author.columns.id }),
published: column.boolean({ default: false }),
views: column.number({ default: 0 }),
publishedAt: column.date({ optional: true }),
},
});
const Comment = defineTable({
columns: {
id: column.number({ primaryKey: true }),
postId: column.number({ references: () => Post.columns.id }),
content: column.text(),
authorEmail: column.text(),
approved: column.boolean({ default: false }),
createdAt: column.date({ default: NOW }),
},
});
export default defineDb({
tables: { Author, Post, Comment },
});
DB Seed-Daten und Queries
Astro DB bietet typsichere Drizzle-Queries direkt in .astro-Dateien:
// db/seed.ts — Entwicklungs-Daten
import { db, Author, Post } from 'astro:db';
export default async function seed() {
// Autor anlegen
await db.insert(Author).values([
{ name: 'Max Mustermann', email: 'max@example.com', bio: 'Astro-Enthusiast' },
{ name: 'Anna Schmidt', email: 'anna@example.com' },
]);
// Posts anlegen
await db.insert(Post).values([
{
slug: 'astro-islands-erklaert',
title: 'Astro Islands erklärt',
body: 'Islands Architecture in der Praxis...',
authorId: 1,
published: true,
},
]);
}
---
import { db, Post, Author, eq, desc, and } from 'astro:db';
// Einfache Query: alle publizierten Posts
const posts = await db.select().from(Post)
.where(eq(Post.published, true))
.orderBy(desc(Post.publishedAt));
// JOIN: Posts mit Author-Namen
const postsWithAuthors = await db
.select({
title: Post.title,
slug: Post.slug,
authorName: Author.name,
views: Post.views,
})
.from(Post)
.innerJoin(Author, eq(Post.authorId, Author.id))
.where(
and(
eq(Post.published, true),
eq(Post.authorId, 1)
)
);
// Views erhöhen (in API Route)
await db.update(Post)
.set({ views: sql`${Post.views} + 1` })
.where(eq(Post.slug, slug));
Lokale vs. Production DB: In der Entwicklung nutzt Astro DB SQLite (keine Konfiguration nötig). In Production muss ASTRO_DB_REMOTE_URL und ASTRO_DB_APP_TOKEN gesetzt sein. Claude Code generiert automatisch die passenden Environment-Variablen-Dokumentation.
6. Performance — Core Web Vitals und View Transitions
Astro-Websites erreichen in unabhängigen Benchmarks konsistent bessere Core Web Vitals als Next.js oder Remix — hauptsächlich weil standardmäßig kein JavaScript ausgeliefert wird. Mit View Transitions kommen SPA-ähnliche Navigation-Animationen ohne den JavaScript-Overhead einer echten SPA.
PERF View Transitions API — SPA-Navigation
Astro 3+ integriert die native View Transitions API für fließende Seitenübergänge:
---
import { ViewTransitions } from 'astro:transitions';
---
<html lang="de">
<head>
<title>{title}</title>
<ViewTransitions /> <!-- Aktiviert SPA-Navigation sitewide -->
</head>
<body>
<slot />
</body>
</html>
---
<!-- Blog-Listing: Hero-Bild mit Namen versehen -->
<img
src={post.data.image}
alt={post.data.title}
transition:name={`hero-${post.slug}`}
/>
<h2 transition:name={`title-${post.slug}`}>
{post.data.title}
</h2>
<!-- Blog-Detail: Dasselbe Element → flüssige Morphing-Animation -->
<img
src={post.data.image}
alt={post.data.title}
transition:name={`hero-${post.slug}`} <!-- Gleicher Name! -->
/>
<h1 transition:name={`title-${post.slug}`}>
{post.data.title}
</h1>
<!-- Transition-Animationen anpassen -->
<aside transition:name="sidebar" transition:animate="slide">
<Sidebar />
</aside>
<!-- Element persistent halten (kein Re-Mount bei Navigation) -->
<audio transition:persist src="/podcast.mp3" controls />
PERF Performance-Vergleich 2026
Realwelt-Benchmarks für content-heavy Websites (Blog mit 50 Seiten, 10 interaktiven Komponenten):
// Lighthouse Scores — Realwelt-Vergleich 2026
// (content-heavy Blog, ähnliche Funktionalität)
Framework | LCP | TBT | CLS | JS Bundle | Lighthouse
-----------------+--------+--------+--------+-----------+-----------
Astro 5 (static) | 0.8s | 0ms | 0.001 | ~5 KB | 99-100
Astro 5 (SSR) | 1.1s | 10ms | 0.001 | ~5 KB | 96-99
Next.js 15 | 2.1s | 180ms | 0.05 | ~89 KB | 78-85
Remix 2 | 1.8s | 120ms | 0.03 | ~72 KB | 82-88
Gatsby 5 | 1.5s | 90ms | 0.02 | ~65 KB | 85-90
// Astro-Vorteil: Islands senden NUR das JS der interaktiven Komponenten
// Beispiel: 3 React-Islands auf einer Seite → ~23 KB (nicht 89 KB)
// Optimal: Bild-Optimierung mit Astro Image
import { Image, Picture } from 'astro:assets';
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---
<!-- Automatisch: WebP-Konvertierung, srcset, lazy loading, korrekte Dimensionen -->
<Image
src={heroImage}
alt="Hero Bild"
width={1200}
height={630}
loading="eager" <!-- LCP-Element: eager statt lazy! -->
format="webp"
quality={80}
/>
PERF Prefetching und Asset-Optimierung
Astro 5 bietet eingebautes Prefetching für noch schnellere Navigation:
// astro.config.mjs — Prefetch-Konfiguration
export default defineConfig({
prefetch: {
prefetchAll: true, // Alle Links im Viewport prefetchen
defaultStrategy: 'viewport', // Alternativen: 'hover', 'tap', 'load'
},
image: {
// Remote-Bilder erlauben (für externe Quellen)
domains: ['images.unsplash.com', 'cdn.example.com'],
// Oder Wildcard:
remotePatterns: [{ protocol: 'https' }],
},
compressHTML: true, // HTML-Ausgabe komprimieren
vite: {
build: {
rollupOptions: {
output: {
// Manuelle Chunks für besseres Caching
manualChunks: {
react: ['react', 'react-dom'],
},
},
},
},
},
});
---
<!-- data-astro-prefetch: Link explizit prefetchen -->
<a href="/wichtige-seite" data-astro-prefetch="hover">
Wichtige Seite
</a>
<!-- Prefetch deaktivieren für externe Links -->
<a href="https://external.com" data-astro-prefetch="false">
Externer Link
</a>
Claude Code und Astro 2026: Claude Code kennt den kompletten Astro-5-Ökosystem-Stack: alle Integrationen (@astrojs/react, @astrojs/vue, @astrojs/mdx, @astrojs/sitemap, @astrojs/rss), alle Adapter, Astro DB, View Transitions und die experimentellen Features wie Server Islands (Astro 5). Ein einziger Prompt wie "baue mir einen Blog mit Content Collections, Kommentar-Funktion via Astro DB und View Transitions" generiert vollständig funktionierenden, deploybasierten Code.
Die Kombination aus Zero-JS-by-Default, typsicheren Content Collections, nahtlosem SSR und eingebautem ORM macht Astro 2026 zur stärksten Wahl für Marketing-Sites, Blogs, Dokumentationsportale und alle Anwendungen, bei denen Performance und Content im Vordergrund stehen. Claude Code versteht diesen Stack vollständig und kann komplette Astro-Projekte von der Konfiguration bis zum Deployment aufbauen — in einem Bruchteil der Zeit, die manuelle Entwicklung erfordert.
Astro-Modul im Kurs
Im Claude Code Mastery Kurs: vollständiges Astro-Modul mit Islands Architecture, Content Collections, Astro DB und Deployment auf Vercel/Cloudflare Pages.
14 Tage kostenlos testen →