Astro mit Claude Code: Content Sites & Static Generation 2026

Astro hat sich als das Framework der Wahl für Content-getriebene Websites etabliert. Mit Claude Code beschleunigt sich die Entwicklung dramatisch: Content Collections mit vollständiger TypeScript-Typisierung, Islands Architecture für selektive Interaktivität und automatische Performance-Optimierung — alles in einem Workflow. Dieser Artikel zeigt, wie du hochperformante Astro-Sites 2026 mit KI-Unterstützung baust.

1. Astro Setup & Projekt-Struktur

Astro-Projekte starten blitzschnell. Claude Code kennt den gesamten Setup-Prozess und generiert eine saubere, skalierbare Projektstruktur — inklusive TypeScript-Konfiguration und sinnvoller Ordner-Hierarchie.

Projekt initialisieren

# Neues Astro-Projekt erstellen npm create astro@latest my-content-site # Wizard-Optionen: # ✓ Include sample files # ✓ Install dependencies # ✓ Initialize a git repository # ✓ TypeScript: Strict cd my-content-site npm run dev # → http://localhost:4321

Claude Code Prompt für ein vollständiges Setup:

Prompt

"Erstelle ein Astro 5-Projekt mit TypeScript strict mode, Content Collections für Blog und Docs, Tailwind CSS, React-Integration für interaktive Komponenten und einer sauberen Ordnerstruktur. Inkludiere astro.config.mjs mit allen notwendigen Integrationen."

Projektstruktur

my-content-site/ ├── src/ │ ├── pages/ # Dateibasiertes Routing │ │ ├── index.astro │ │ ├── blog/ │ │ │ ├── index.astro │ │ │ └── [slug].astro # Dynamische Route │ │ └── docs/ │ │ └── [...slug].astro # Catch-all Route │ ├── layouts/ # Basis-Layouts │ │ ├── BaseLayout.astro │ │ ├── BlogLayout.astro │ │ └── DocsLayout.astro │ ├── components/ # Astro + Framework-Komponenten │ │ ├── Header.astro │ │ ├── Footer.astro │ │ ├── BlogCard.astro │ │ └── SearchWidget.tsx # React Island │ └── content/ # Content Collections │ ├── blog/ │ └── docs/ ├── public/ # Statische Assets │ ├── favicon.svg │ └── og-image.png ├── astro.config.mjs ├── tsconfig.json └── package.json

astro.config.mjs — Vollständige Konfiguration

import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; import tailwind from '@astrojs/tailwind'; import sitemap from '@astrojs/sitemap'; import mdx from '@astrojs/mdx'; export default defineConfig({ site: 'https://agentic-movers.com', integrations: [ react(), tailwind({ applyBaseStyles: false }), sitemap(), mdx(), ], output: 'static', // SSG (default) image: { service: { entrypoint: 'astro/assets/services/sharp' }, }, markdown: { shikiConfig: { theme: 'one-dark-pro', wrap: true, }, }, vite: { define: { __BUILD_DATE__: JSON.stringify(new Date().toISOString()), }, }, });
Claude Code Tipp: Claude kennt alle Astro-Integrationen und schlaegt automatisch die richtigen npm-Pakete vor. Einfach den gewuenschten Feature-Stack beschreiben und die komplette astro.config.mjs wird generiert — inkllusve korrekter Import-Reihenfolge und Kompatibilitaets-Checks.

2. Content Collections & Type Safety

Content Collections sind Astros staerkste Funktion fuer Content-Sites. Sie kombinieren Markdown/MDX mit vollstaendiger TypeScript-Typisierung — kein manuelles Parsen, keine Runtime-Fehler durch falsche Frontmatter-Felder. Claude Code generiert das komplette Schema auf Basis deiner Anforderungen.

Collection Schema definieren

// src/content/config.ts import { defineCollection, z } from 'astro:content'; const blogCollection = defineCollection({ type: 'content', schema: z.object({ title: z.string().min(10).max(100), description: z.string().min(50).max(300), pubDate: z.coerce.date(), updatedDate: z.coerce.date().optional(), author: z.string().default('Agentic Movers Team'), category: z.enum(['tutorial', 'news', 'case-study', 'guide']), tags: z.array(z.string()).default([]), draft: z.boolean().default(false), heroImage: z.string().optional(), readTime: z.number().optional(), // Minuten seo: z.object({ canonical: z.string().url().optional(), noindex: z.boolean().default(false), ogImage: z.string().optional(), }).optional(), }), }); const docsCollection = defineCollection({ type: 'content', schema: z.object({ title: z.string(), order: z.number(), section: z.string(), draft: z.boolean().default(false), lastEdit: z.coerce.date(), }), }); export const collections = { 'blog': blogCollection, 'docs': docsCollection, };

Daten abrufen — getCollection & getEntry

// src/pages/blog/index.astro --- import { getCollection } from 'astro:content'; import type { CollectionEntry } from 'astro:content'; import BaseLayout from '../layouts/BaseLayout.astro'; import BlogCard from '../components/BlogCard.astro'; // Alle Blog-Posts laden (ohne Drafts, sortiert nach Datum) const allPosts: CollectionEntry<'blog'>[] = await getCollection( 'blog', ({ data }) => !data.draft ); const sortedPosts = allPosts.sort( (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf() ); // Posts nach Kategorie gruppieren const postsByCategory = sortedPosts.reduce< Record<string, CollectionEntry<'blog'>[]> >((acc, post) => { const cat = post.data.category; acc[cat] = [...(acc[cat] ?? []), post]; return acc; }, {}); --- <BaseLayout title="Blog"> <h1>Blog</h1> {Object.entries(postsByCategory).map(([category, posts]) => ( <section> <h2>{category}</h2> {posts.map(post => <BlogCard post={post} />)} </section> ))} </BaseLayout>

Dynamische Routen mit [slug].astro

// src/pages/blog/[slug].astro --- import { getCollection, getEntry } from 'astro:content'; import type { GetStaticPaths } from 'astro'; import BlogLayout from '../../layouts/BlogLayout.astro'; export const getStaticPaths: GetStaticPaths = async () => { const posts = await getCollection('blog', ({ data }) => !data.draft); return posts.map(post => ({ params: { slug: post.slug }, props: { post }, })); }; interface Props { post: CollectionEntry<'blog'>; } const { post } = Astro.props; const { Content, headings } = await post.render(); --- <BlogLayout title={post.data.title} description={post.data.description} pubDate={post.data.pubDate} headings={headings} > <Content /> </BlogLayout>
Content Collection

Der entscheidende Vorteil von Content Collections: Das Schema wird zur Build-Zeit validiert. Fehlende oder falsch typisierte Frontmatter-Felder fuehren zu Build-Errors — keine Runtime-Ueberraschungen. Claude Code generiert Schemas direkt aus deinen bestehenden Markdown-Dateien.

3. Islands Architecture

Islands Architecture ist Astros revolutionaeres Konzept: Standardmaessig kein JavaScript im Browser. Interaktive Komponenten werden als isolierte "Islands" gerendert — mit expliziter Kontrolle darueber, wann und wie sie hydratisiert werden. Claude Code unterstuetzt alle client:-Direktiven und erklaert die Trade-offs.

Hydration-Direktiven im Vergleich

// src/pages/index.astro --- import SearchWidget from '../components/SearchWidget.tsx'; import NewsletterForm from '../components/NewsletterForm.tsx'; import VideoPlayer from '../components/VideoPlayer.tsx'; import Analytics from '../components/Analytics.tsx'; import PricingTable from '../components/PricingTable.vue'; --- <!-- client:load — sofort hydratisieren (Above the Fold) --> <SearchWidget client:load /> <!-- client:visible — hydratisieren wenn sichtbar (Intersection Observer) --> <VideoPlayer client:visible /> <PricingTable client:visible /> <!-- Vue-Komponente! --> <!-- client:idle — hydratisieren wenn Browser idle (requestIdleCallback) --> <Analytics client:idle /> <!-- client:media — hydratisieren bei Media Query Match --> <MobileMenu client:media="(max-width: 768px)" /> <!-- client:only — KEIN SSR, nur Client-Rendering --> <LiveChat client:only="react" /> <!-- Kein Directive = statisches HTML, 0 KB JavaScript --> <StaticHeroSection />

React Island mit TypeScript

// src/components/SearchWidget.tsx import { useState, useCallback, useEffect } from 'react'; interface SearchResult { slug: string; title: string; description: string; category: string; score: number; } interface SearchWidgetProps { placeholder?: string; maxResults?: number; } export default function SearchWidget({ placeholder = 'Artikel suchen...', maxResults = 5, }: SearchWidgetProps) { const [query, setQuery] = useState(''); const [results, setResults] = useState<SearchResult[]>([]); const [loading, setLoading] = useState(false); const [index, setIndex] = useState<any[]>([]); // Search-Index beim Laden abrufen useEffect(() => { fetch('/search-index.json') .then(r => r.json()) .then(setIndex); }, []); const handleSearch = useCallback(async (q: string) => { if (q.length < 2) { setResults([]); return; } setLoading(true); // Client-seitige Fuzzy-Suche const filtered = index .filter(item => item.title.toLowerCase().includes(q.toLowerCase()) || item.description.toLowerCase().includes(q.toLowerCase())) .slice(0, maxResults); setResults(filtered); setLoading(false); }, [index, maxResults]); return ( <div className="search-widget"> <input type="search" value={query} onChange={e => { setQuery(e.target.value); handleSearch(e.target.value); }} placeholder={placeholder} className="search-input" /> {loading && <span className="search-loading">Suche...</span>} {results.length > 0 && ( <ul className="search-results"> {results.map(r => ( <li key={r.slug}> <a href={`/blog/${r.slug}`>} <strong>{r.title}</strong> <span>{r.description}</span> </a> </li> ))} </ul> )} </div> ); }

Vue Island — Multi-Framework-Integration

<!-- src/components/PricingTable.vue --> <script setup lang="ts"> import { ref, computed } from 'vue'; interface Plan { id: string; name: string; price: number; features: string[]; } const plans: Plan[] = [ { id: 'starter', name: 'Starter', price: 29, features: ['5 Projekte', 'API-Zugang'] }, { id: 'pro', name: 'Pro', price: 79, features: ['∞ Projekte', 'Priority Support'] }, ]; const selectedPlan = ref<string>('pro'); const annual = ref(true); const displayPrice = computed(() => (plan: Plan) => annual.value ? Math.round(plan.price * 10) : plan.price ); </script> <template> <div class="pricing-table"> <div class="billing-toggle"> <button @click="annual = false" :class="{ active: !annual }">Monatlich</button> <button @click="annual = true" :class="{ active: annual }">Jaehrlich −17%</button> </div> <div class="plans"> <div v-for="plan in plans" :key="plan.id" @click="selectedPlan = plan.id" :class="['plan-card', { selected: selectedPlan === plan.id }]"> <h3>{{ plan.name }}</h3> <p class="price">€{{ displayPrice(plan) }}/mo</p> <ul> <li v-for="feature in plan.features">{{ feature }}</li> </ul> </div> </div> </div> </template>
Islands Best Practice: Verwende client:visible fuer alle Komponenten below the fold. Das spart Initial-JS und verbessert den LCP-Score erheblich. Claude Code analysiert deine Komponentenstruktur und schlaegt automatisch die optimale Hydration-Strategie vor.

4. View Transitions & Animations

Astros View Transitions API ermoeglicht native Page-Animationen ohne Single-Page-Application-Overhead. Seiten bleiben vollstaendig statisch, der Browser uebernimmt smooth Uebergaenge. Claude Code generiert anspruchsvolle Transition-Konfigurationen auf Knopfdruck.

View Transitions aktivieren

<!-- src/layouts/BaseLayout.astro --> --- import { ViewTransitions } from 'astro:transitions'; interface Props { title: string; description?: string; } const { title, description = 'Agentic Movers Blog' } = Astro.props; --- <!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <title>{title}</title> <meta name="description" content={description}> <!-- View Transitions einbinden --> <ViewTransitions /> </head> <body> <slot /> </body> </html>

Benannte Transitions mit transition:name

<!-- Blog-Liste: src/components/BlogCard.astro --> --- import type { CollectionEntry } from 'astro:content'; interface Props { post: CollectionEntry<'blog'>; } const { post } = Astro.props; --- <article class="blog-card"> <!-- transition:name = eindeutige ID → matched auf Detail-Seite --> <img src={post.data.heroImage} alt={post.data.title} transition:name={`hero-image-${post.slug}`} transition:animate="fade" /> <h2 transition:name={`title-${post.slug}`} transition:animate="slide" > <a href={`/blog/${post.slug}`>{post.data.title}</a> </h2> </article> <!-- Blog-Detail: src/pages/blog/[slug].astro --> --- const { post } = Astro.props; --- <img src={post.data.heroImage} alt={post.data.title} transition:name={`hero-image-${post.slug}`} <!-- Same name → morphs! --> /> <h1 transition:name={`title-${post.slug}`> {post.data.title} </h1>

Benutzerdefinierte Animationen

<!-- Custom Transition-Animation definieren --> <div transition:animate={{ forwards: { old: [{ opacity: 1, transform: 'translateX(0)' }, { opacity: 0, transform: 'translateX(-100px)' }], new: [{ opacity: 0, transform: 'translateX(100px)' }, { opacity: 1, transform: 'translateX(0)' }], }, backwards: { old: [{ opacity: 1, transform: 'translateX(0)' }, { opacity: 0, transform: 'translateX(100px)' }], new: [{ opacity: 0, transform: 'translateX(-100px)' }, { opacity: 1, transform: 'translateX(0)' }], }, defaultProps: { duration: '300ms', easing: 'ease-in-out' }, }} > <slot /> </div>

Programmatische Navigation

// src/components/SearchWithTransition.tsx import { navigate } from 'astro:transitions/client'; export default function SearchWithTransition() { const handleSelect = async (slug: string) => { // View Transition beim programmatischen Navigieren await navigate(`/blog/${slug}`, { history: 'push', info: { trigger: 'search' }, }); }; return <SearchWidget onSelect={handleSelect} />; }
Islands + Transitions

View Transitions funktionieren auch mit Islands-Komponenten. React/Vue-State wird bei gleichen Komponenten-Keys automatisch beibehalten — kein manuelles State-Management fuer Page-Transitions erforderlich.

5. SSR & Hybrid Rendering

Astro unterstuetzt drei Rendering-Modi: Static (SSG), Server (vollstaendiges SSR) und Hybrid (SSG + SSR gemischt). Claude Code hilft dabei, die optimale Strategie pro Seite zu waehlen und API-Routes, Middleware und Cookie-Handling korrekt zu implementieren.

Hybrid-Mode konfigurieren

// astro.config.mjs — Hybrid Rendering export default defineConfig({ output: 'hybrid', // SSG by default, opt-in zu SSR adapter: node({ mode: 'standalone', }), }); // Seite als SSR markieren: // src/pages/dashboard.astro --- export const prerender = false; // Diese Seite = SSR --- // Seite explizit als SSG markieren (in server-mode): --- export const prerender = true; // Statisch bauen ---

API-Routes mit TypeScript

// src/pages/api/subscribe.ts import type { APIRoute } from 'astro'; export const POST: APIRoute = async ({ request, cookies }) => { const body = await request.json() as { email: string; name?: string }; // Input-Validierung if (!body.email || !body.email.includes('@')) { return new Response( JSON.stringify({ error: 'Ungueltige E-Mail-Adresse' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } try { // Newsletter-Service anbinden const response = await fetch('https://api.mailerlite.com/api/v2/subscribers', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-MailerLite-ApiKey': import.meta.env.MAILERLITE_KEY, }, body: JSON.stringify({ email: body.email, name: body.name }), }); if (!response.ok) throw new Error('Mailerlite API Fehler'); // Session-Cookie setzen cookies.set('subscribed', 'true', { maxAge: 60 * 60 * 24 * 365, httpOnly: true, secure: import.meta.env.PROD, sameSite: 'lax', }); return new Response( JSON.stringify({ success: true, message: 'Erfolgreich angemeldet!' }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } catch (err) { console.error('Subscribe error:', err); return new Response( JSON.stringify({ error: 'Serverfehler, bitte spaeter erneut versuchen' }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); } }; // GET nicht erlaubt export const GET: APIRoute = () => new Response(null, { status: 405 });

Middleware für Auth & Request-Handling

// src/middleware.ts import { defineMiddleware, sequence } from 'astro:middleware'; // Auth-Middleware const authMiddleware = defineMiddleware(async (ctx, next) => { const { pathname } = ctx.url; // Geschuetzte Routen if (pathname.startsWith('/dashboard')) { const token = ctx.cookies.get('auth-token')?.value; if (!token) { return ctx.redirect(`/login?redirect=${encodeURIComponent(pathname)}`); } // Token validieren (Beispiel) try { const user = await validateToken(token); ctx.locals.user = user; } catch { ctx.cookies.delete('auth-token'); return ctx.redirect('/login'); } } return next(); }); // Logging-Middleware const loggingMiddleware = defineMiddleware(async (ctx, next) => { const start = Date.now(); const response = await next(); console.log(`${ctx.request.method} ${ctx.url.pathname} — ${Date.now()-start}ms`); return response; }); // Middleware-Chain export const onRequest = sequence(loggingMiddleware, authMiddleware); // TypeScript: Locals typisieren // src/env.d.ts declare namespace App { interface Locals { user?: { id: string; email: string; role: string }; } }
Hybrid Rendering Strategie: Marketing-Seiten, Blog-Posts und Docs als SSG (prerender = true), dynamische Bereiche wie Dashboard, API-Endpoints und personalisierte Inhalte als SSR (prerender = false). Claude Code analysiert deine Routing-Struktur und schlaegt die optimale Aufteilung automatisch vor.

6. Performance & Deployment

Astro liefert out-of-the-box Lighthouse-Scores nahe 100 — durch 0 KB JavaScript by default, automatische Bild-Optimierung und perfekte Core Web Vitals. Claude Code unterstuetzt alle offiziellen Adapter und optimiert die Deployment-Konfiguration.

Core Web Vitals — Astro vs. Klassische Frameworks

Metrik Astro (Static) Next.js App Router Create React App
LCP (Largest Contentful Paint) 0.8s ✓ 1.4s 2.8s
INP (Interaction to Next Paint) 12ms ✓ 45ms 180ms
CLS (Cumulative Layout Shift) 0.01 ✓ 0.05 0.12
JS-Bundle (initial) 0 KB ✓ ~120 KB ~200 KB
Lighthouse Score 98–100 ✓ 85–92 60–75

Bild-Optimierung mit Astro Image

// src/components/OptimizedHero.astro --- import { Image, Picture } from 'astro:assets'; import heroImg from '../assets/hero.png'; // Statischer Import! --- <!-- Automatisch: WebP/AVIF Konvertierung, Lazy Loading, korrekte Dimensions --> <Image src={heroImg} alt="Hero Image" width={1200} height={600} format="avif" quality={80} loading="eager" <!-- LCP-Bild: eager laden --> /> <!-- Picture: mehrere Formate + Art Direction --> <Picture src={heroImg} formats={['avif', 'webp']} widths={[400, 800, 1200]} sizes="(max-width: 800px) 100vw, 1200px" alt="Responsive Hero" />

Vercel Deployment

# Vercel Adapter installieren npm install @astrojs/vercel // astro.config.mjs import vercel from '@astrojs/vercel/serverless'; export default defineConfig({ output: 'hybrid', adapter: vercel({ imageService: true, // Vercel Image Optimization nutzen edgeMiddleware: true, // Middleware auf Edge (schneller) webAnalytics: { enabled: true }, speedInsights: { enabled: true }, }), });

Node.js Deployment (Self-Hosted)

# Node.js Adapter npm install @astrojs/node // astro.config.mjs import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), }); # Build + Start npm run build # → dist/server/entry.mjs node dist/server/entry.mjs # Docker (Beispiel) FROM node:20-alpine WORKDIR /app COPY dist/ ./dist/ COPY package.json ./ RUN npm install --omit=dev ENV HOST=0.0.0.0 PORT=3000 EXPOSE 3000 CMD ["node", "./dist/server/entry.mjs"]

Build-Optimierung & Analyse

# Bundle analysieren npm install --save-dev rollup-plugin-visualizer // astro.config.mjs import { visualizer } from 'rollup-plugin-visualizer'; export default defineConfig({ vite: { plugins: [ visualizer({ filename: './dist/bundle-stats.html', gzipSize: true, brotliSize: true, open: true, }), ], build: { rollupOptions: { output: { manualChunks: { // Vendor-Chunks trennen 'react-vendor': ['react', 'react-dom'], 'vue-vendor': ['vue'], }, }, }, }, }, });
Performance

Astro baut HTML-Dateien, keine JavaScript-Bundles fuer statische Seiten. Das bedeutet: instant First Contentful Paint, kein JavaScript-Parsing-Overhead, perfektes SEO. Claude Code analysiert deinen Build-Output und gibt konkrete Empfehlungen fuer weitere Optimierungen.

Weitere Optimierungen mit Claude Code

// Preload-Hints automatisch generieren --- const criticalCSS = await Astro.glob('../styles/critical.css'); --- <head> <!-- Critical CSS inline --> <style set:html={criticalCSS[0].default} /> <!-- Non-critical CSS async laden --> <link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> <!-- Preconnect fuer externe Domains --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="dns-prefetch" href="https://cdn.example.com"> <!-- LCP-Bild preloaden --> <link rel="preload" as="image" href="/hero.avif" imagesrcset="/hero-400.avif 400w, /hero-800.avif 800w" imagesizes="100vw"> </head>

Fazit: Astro & Claude Code — Das perfekte Duo fuer Content Sites

Astro 5 mit TypeScript ist 2026 die klare Wahl fuer Content-getriebene Websites, Blogs, Dokumentationen und Marketing-Seiten. Die Kombination aus Content Collections, Islands Architecture, View Transitions und flexiblem SSR/SSG-Hybrid macht es einzigartig leistungsfaehig.

Claude Code beschleunigt die Astro-Entwicklung in jeder Phase: vom initialen Projekt-Setup ueber komplexe Content-Collection-Schemas bis hin zu Performance-Optimierungen und Deployment-Konfigurationen. Statt Dokumentationen zu waelzen, beschreibt man das gewuenschte Ergebnis — Claude Code generiert typsicheren, produktionsreifen Astro-Code.

Kernvorteile im Ueberblick:

  • 0 KB JavaScript by default — maximale Performance ohne Konfiguration
  • Content Collections — vollstaendige TypeScript-Typisierung fuer Markdown/MDX
  • Islands Architecture — selektive Hydratisierung mit client:* Direktiven
  • View Transitions — native Page-Animationen ohne SPA-Overhead
  • Multi-Framework — React, Vue, Svelte, Solid nebeneinander
  • Hybrid Rendering — SSG + SSR flexibel per Seite konfigurierbar
  • Claude Code Integration — von Setup bis Deployment KI-unterstuetzt

Astro-Modul im Kurs

Im Claude Code Mastery Kurs: vollstaendiges Astro-Modul mit Content Collections, Islands Architecture, View Transitions und SSR fuer hochperformante Content Sites.

14 Tage kostenlos testen →