Astro Web Framework
Überblick
Astro ist ein Webframework, das für inhaltsreiche Websites entwickelt wurde – Blogs, Dokumentationen, Portfolios, Marketingseiten und E-Commerce. Die Kerninnovation ist die Inselarchitektur: Standardmäßig sendet Astro null JavaScript an den Browser. Interaktive Komponenten werden selektiv als isolierte „Inseln“ hydriert. Astro unterstützt gleichzeitig React, Vue, Svelte, Solid und andere UI-Frameworks im selben Projekt, sodass du pro Komponente das passende Werkzeug wählen kannst.
Wann du diese Fertigkeit einsetzen solltest
- Verwende sie beim Erstellen eines Blogs, einer Dokumentationsseite, einer Marketingseite oder eines Portfolios
- Verwende sie, wenn Leistung und Core Web Vitals höchste Priorität haben
- Verwende sie, wenn das Projekt inhaltslastig mit Markdown- oder MDX-Dateien ist
- Verwende sie, wenn du eine statische (SSG) Ausgabe mit optionalem SSR für dynamische Routen wünschst
- Verwende sie, wenn der Benutzer nach
.astro-Dateien,Astro.props, Content-Collections oderclient:-Direktiven fragt
So funktioniert es
Schritt 1: Projekteinrichtung
npm create astro@latest my-site
cd my-site
npm install
npm run dev
Füge Integrationen nach Bedarf hinzu:
npx astro add tailwind # Tailwind CSS
npx astro add react # React-Komponentenunterstützung
npx astro add mdx # MDX-Unterstützung
npx astro add sitemap # Automatische sitemap.xml
npx astro add vercel # Vercel SSR-Adapter
Projektstruktur:
src/
pages/ ← Dateibasiertes Routing (.astro, .md, .mdx)
layouts/ ← Wiederverwendbare Seitenhüllen
components/ ← UI-Komponenten (.astro, .tsx, .vue, etc.)
content/ ← Typsichere Content-Collections (Markdown/MDX)
styles/ ← Globales CSS
public/ ← Statische Assets (werden unverändert kopiert)
astro.config.mjs ← Framework-Konfiguration
Schritt 2: Astro-Komponentensyntax
.astro-Dateien haben einen Code-Bereich oben (nur Server) und eine Vorlage darunter:
---
// src/components/Card.astro
// Dieser Block läuft NUR auf dem Server – niemals im Browser
interface Props {
title: string;
href: string;
description: string;
}
const { title, href, description } = Astro.props;
---
<article class="card">
<h2><a href={href}>{title}</a></h2>
<p>{description}</p>
</article>
<style>
/* Automatisch auf diese Komponente beschränkt */
.card { border: 1px solid #eee; padding: 1rem; }
</style>
Schritt 3: Dateibasierte Seiten und Routing
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/blog/[slug].astro → /blog/:slug (dynamisch)
src/pages/blog/[...path].astro → /blog/* (Auffangpfad)
Dynamische Route mit getStaticPaths:
---
// src/pages/blog/[slug].astro
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();
---
<h1>{post.data.title}</h1>
<Content />
Schritt 4: Content-Collections
Content-Collections bieten typsicheren Zugriff auf Markdown- und MDX-Dateien:
// src/content/config.ts
import { z, defineCollection } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter(p => !p.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
<time>{post.data.date.toLocaleDateString()}</time>
</li>
))}
</ul>
Schritt 5: Inseln – Selektive Hydrierung
Standardmäßig rendern UI-Framework-Komponenten zu statischem HTML ohne JS. Verwende client:-Direktiven zum Hydrieren:
---
import Counter from '../components/Counter.tsx'; // React-Komponente
import VideoPlayer from '../components/VideoPlayer.svelte';
---
<!-- Statisches HTML – kein JavaScript wird an den Browser gesendet -->
<Counter initialCount={0} />
<!-- Sofort beim Seitenladen hydrieren -->
<Counter initialCount={0} client:load />
<!-- Hydrieren, wenn die Komponente in den sichtbaren Bereich scrollt -->
<VideoPlayer src="/demo.mp4" client:visible />
<!-- Nur hydrieren, wenn der Browser im Leerlauf ist -->
<Analytics client:idle />
<!-- Nur bei einer bestimmten Media-Query hydrieren -->
<MobileMenu client:media="(max-width: 768px)" />
Schritt 6: Layouts
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = 'My Astro Site' } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<nav>...</nav>
<main>
<slot /> <!-- Seiteninhalt wird hier gerendert -->
</main>
<footer>...</footer>
</body>
</html>
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="About Us">
<h1>About Us</h1>
<p>Welcome to our company...</p>
</BaseLayout>
Schritt 7: SSR-Modus (On-Demand Rendering)
Aktiviere SSR für dynamische Seiten durch Festlegen eines Adapters:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'hybrid', // 'static' | 'server' | 'hybrid'
adapter: vercel(),
});
Optiere einzelne Seiten in SSR mit export const prerender = false.
Beispiele
Beispiel 1: Blog mit RSS-Feed
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: 'My Blog',
description: 'Latest posts',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.date,
link: `/blog/${post.slug}/`,
})),
});
}
Beispiel 2: API-Endpunkt (SSR)
// src/pages/api/subscribe.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const { email } = await request.json();
if (!email) {
return new Response(JSON.stringify({ error: 'Email required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
await addToNewsletter(email);
return new Response(JSON.stringify({ success: true }), { status: 200 });
};
Beispiel 3: React-Komponente als Insel
// src/components/SearchBox.tsx
import { useState } from 'react';
export default function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
async function search(e: React.FormEvent) {
e.preventDefault();
const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
setResults(data);
}
return (
<form onSubmit={search}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">Search</button>
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</form>
);
}
---
import SearchBox from '../components/SearchBox.tsx';
---
<!-- Sofort hydriert – diese Insel ist interaktiv -->
<SearchBox client:load />
Best Practices
- ✅ Halte die meisten Komponenten als statische
.astro-Dateien – hydriere nur, was interaktiv sein muss - ✅ Verwende Content-Collections für alle Markdown/MDX-Inhalte – du erhältst Typsicherheit und automatische Validierung
- ✅ Bevorzuge
client:visiblegegenüberclient:loadfür Komponenten unterhalb des Bildschirmrands, um anfängliches JS zu reduzieren - ✅ Verwende
import.meta.envfür Umgebungsvariablen – stelle öffentlichen VariablenPUBLIC_voran - ✅ Füge
<ViewTransitions />vonastro:transitionsfür flüssige Seitennavigation ohne vollständige SPA hinzu - ❌ Verwende nicht
client:loadbei jeder Komponente – das hebt Astros Leistungsvorteil auf - ❌ Lege keine Geheimnisse in
.astro-Frontmatter, das in clientseitigen Vorlagen verwendet wird - ❌ Überspringe nicht
getStaticPathsfür dynamische Routen im statischen Modus – Builds werden fehlschlagen
Sicherheitshinweise
- Frontmatter-Code in
.astro-Dateien läuft nur serverseitig und wird niemals im Browser angezeigt. - Verwende
import.meta.env.PUBLIC_*nur für unkritische Werte. Private Umgebungsvariablen (ohnePUBLIC_-Präfix) werden niemals an den Client gesendet. - Validiere im SSR-Modus alle
Astro.request-Eingaben vor Datenbankabfragen oder API-Aufrufen. - Säubere alle benutzergenerierten Inhalte vor der Darstellung mit
set:html– es umgeht die automatische Escaping-Funktion.
Häufige Fallstricke
-
Problem: JavaScript aus einer React/Vue-Komponente wird im Browser nicht ausgeführt
Lösung: Füge eineclient:-Direktive hinzu (client:load,client:visible, usw.) – ohne sie rendern Komponenten nur statisches HTML. -
Problem:
getStaticPaths-Daten sind nach Inhaltsaktualisierungen während der Entwicklung veraltet
Lösung: Astros Entwicklerserver überwacht Inhaltsdateien – starte neu, wenn Änderungen ancontent/config.tsnicht übernommen werden. -
Problem: Der Typ von
Astro.propsistany– keine Autovervollständigung
Lösung: Definiere einProps-Interface oder einen Typen im Frontmatter – Astro leitet ihn automatisch ab. -
Problem: CSS aus einer
.astro-Komponente überträgt sich auf andere Komponenten
Lösung: Styles in.astro<style>-Tags werden automatisch begrenzt. Verwende:global()nur bei gezielter Ausrichtung auf Kindelemente.
Verwandte Fertigkeiten
@sveltekit– Wenn du ein Full-Stack-Framework mit reaktiver UI benötigst (im Gegensatz zu Astros Inhaltsfokus)@nextjs-app-router-patterns– Wenn du ein React-zentriertes Full-Stack-Framework benötigst@tailwind-patterns– Gestaltung von Astro-Seiten mit Tailwind CSS@progressive-web-app– Hinzufügen von PWA-Funktionen zu einer Astro-Seite
Einschränkungen
- Verwende diese Fertigkeit nur, wenn die Aufgabe eindeutig dem oben beschriebenen Umfang entspricht.
- Betrachte die Ausgabe nicht als Ersatz für umgebungsspezifische Validierung, Tests oder Expertenüberprüfung.
- Frage nach, wenn erforderliche Eingaben, Berechtigungen, Sicherheitsgrenzen oder Erfolgskriterien fehlen.