Static Sites & Content
6. Mai 2026
11 min Lesezeit
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 →