Astro-Operationen
Faktenstand: Juli 2026.
Umfassende Muster für die Entwicklung mit dem Astro-Framework: Inselarchitektur, Content Collections, Rendering-Strategien, View Transitions und plattformübergreifendes Deployment.
Entscheidungsbaum für die Rendering-Strategie
Welche Rendering-Strategie?
│
├─ Ist der Inhalt größtenteils statisch (Blog, Doku, Marketing)?
│ ├─ JA → Ändert er sich seltener als täglich?
│ │ ├─ JA → SSG (output: 'static')
│ │ │ Schnellster TTFB, CDN-cachbar, keine Runtime-Kosten
│ │ └─ NEIN → Hybrid (output: 'hybrid')
│ │ Standardmäßig statisch + opt-in SSR pro Route
│ └─ NEIN → Benötigt jede Seite Personalisierung?
│ ├─ JA → SSR (output: 'server')
│ │ Dynamisch pro Anfrage, authentifizierungsbewusst, Echtzeitdaten
│ └─ NEIN → Hybrid (output: 'hybrid')
│ Statisches Grundgerüst + Server-Inseln für dynamische Teile
│
├─ Benötigt die App Echtzeit-Interaktivität (Dashboard, SPA)?
│ ├─ JA → Ist es eine vollständige SPA mit clientseitigem Routing?
│ │ ├─ JA → Erwäge React/Vue SPA stattdessen, oder Astro + client:only
│ │ └─ NEIN → Hybrid + Inselarchitektur
│ │ Interaktive Inseln in statischen Seiten
│ └─ NEIN → SSG (output: 'static')
│
├─ Bauzeit-Bedenken (>10.000 Seiten)?
│ ├─ JA → Hybrid mit On-Demand-Rendering
│ │ Beliebte Seiten vorgerendert, SSR für den Long Tail
│ └─ NEIN → SSG ist ausreichend
│
└─ Wird Edge Computing benötigt (niedrige Latenz weltweit)?
├─ JA → SSR + Cloudflare/Vercel Edge Adapter
└─ NEIN → SSR + Node-Adapter oder SSG
Konfiguration
// astro.config.mjs
import { defineConfig } from 'astro/config';
// SSG (Standard) - alle Seiten zur Bauzeit vorgerendert
export default defineConfig({
output: 'static',
});
// SSR - alle Seiten werden bei Anfrage gerendert
export default defineConfig({
output: 'server',
adapter: cloudflare(), // oder vercel(), netlify(), node()
});
// Hybrid - statisch als Standard, opt-in SSR pro Seite
export default defineConfig({
output: 'hybrid',
adapter: cloudflare(),
});
---
// Im Hybrid-Modus: Opt-out vom Vorrendern für bestimmte Seiten:
export const prerender = false;
// Im SSR-Modus: Opt-in zum Vorrendern:
export const prerender = true;
---
Kurzreferenz Inselarchitektur
| Direktive | Hydratisiert wenn | JS ausgeliefert | Anwendungsfall |
|---|---|---|---|
client:load | Sofort beim Seitenladen | Volles Bundle | Interaktivität oberhalb der Falz (Navigation, Hero CTA) |
client:idle | Nachdem die Seite im Leerlauf ist (requestIdleCallback) | Volles Bundle | Interaktivität unterhalb der Falz (Kommentarformular, Chat) |
client:visible | Wenn in den Viewport gescrollt | Volles Bundle | Weit unten auf der Seite (Footer-Widget, Karussell) |
client:media | Wenn die Media Query zutrifft | Volles Bundle | Mobil-Navigation, responsive Komponenten |
client:only="react" | Sofort, SSR komplett überspringen | Volles Bundle | Komponenten, die nicht SSR-fähig sind (Canvas, WebGL) |
| (keine) | Niemals - nur statisches HTML | Null JS | Statischer Inhalt, Karten, Kopfzeilen |
---
import NavBar from '../components/NavBar.tsx';
import CommentForm from '../components/CommentForm.tsx';
import ImageCarousel from '../components/ImageCarousel.svelte';
import MobileMenu from '../components/MobileMenu.vue';
import ThreeScene from '../components/ThreeScene.tsx';
---
<!-- Wird sofort geladen - kritische Interaktivität -->
<NavBar client:load />
<!-- Wird geladen, nachdem die Seite im Leerlauf ist - nicht kritisch -->
<CommentForm client:idle />
<!-- Wird geladen, wenn in den Viewport gescrollt - Lazy Loading -->
<ImageCarousel client:visible />
<!-- Wird nur auf Mobilgeräten geladen -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- Nur Client-seitig, kein SSR (WebGL kann nicht auf dem Server laufen) -->
<ThreeScene client:only="react" />
Content Collections Schnellstart
Schema definieren
// src/content.config.ts (Astro 5) oder src/content/config.ts (Astro 4)
import { defineCollection, z, reference } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string().max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
author: reference('authors'), // Referenz auf eine andere Sammlung
}),
});
const authors = defineCollection({
loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
schema: z.object({
name: z.string(),
avatar: z.string(),
bio: z.string(),
socials: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
}).optional(),
}),
});
export const collections = { blog, authors };
Sammlungen abfragen
---
import { getCollection, getEntry } from 'astro:content';
// Alle nicht als Entwurf markierten Blogbeiträge, sortiert nach Datum
const posts = (await getCollection('blog', ({ data }) => !data.draft))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
// Einzelnen Eintrag abrufen
const post = await getEntry('blog', 'my-first-post');
// Eine Referenz auflösen
const author = await getEntry(post.data.author);
// Inhalt rendern
const { Content, headings } = await post.render();
---
<Content />
Content Collections vs. Externes CMS
| Kriterium | Content Collections | Externes CMS (Payload, etc.) |
|---|---|---|
| Inhaltstyp | Lokales Markdown/MDX, Doks, Blogs | Relationale Datenmodelle |
| Autoren | Entwickler (versionskontrolliert) | Redakteure (Admin-UI, Multi-User-Auth) |
| Validierung | Typsicher via Zod zur Bauzeit | CMS-seitige Schemas + API-Verträge |
| Aktualisierungsrhythmus | Wird mit der Website deployed | Unabhängig von Deployments |
| API-Bedarf | Keiner (Bauzeit-Abfragen) | REST/GraphQL für andere Konsumenten |
| Workflow | Git-PRs, einfache Überprüfung | Redaktionelle Workflows, Entwürfe, Rollen |
Faustregel: Mit Content Collections beginnen; erst dann zu einem CMS greifen, wenn Nicht-Entwickler ohne Deployment veröffentlichen müssen oder der Inhalt wirklich relational ist.
Referenz zur Projektstruktur
project-root/
├── astro.config.mjs # Astro-Konfiguration
├── tsconfig.json # TypeScript-Konfiguration (erweitert astro/tsconfigs)
├── package.json
├── public/ # Statische Assets (werden unverändert kopiert)
│ ├── favicon.svg
│ ├── robots.txt
│ └── og-image.png
├── src/
│ ├── pages/ # Dateibasiertes Routing
│ │ ├── index.astro # → /
│ │ ├── about.astro # → /about
│ │ ├── blog/
│ │ │ ├── index.astro # → /blog
│ │ │ └── [slug].astro # → /blog/:slug (dynamisch)
│ │ ├── api/
│ │ │ └── search.ts # → /api/search (API-Endpunkt)
│ │ └── [...slug].astro # → Catch-all/404
│ ├── layouts/
│ │ ├── BaseLayout.astro # HTML-Gerüst, <head>, globale Styles
│ │ └── BlogPost.astro # Blogbeitrag-Layout
│ ├── components/
│ │ ├── Header.astro # Statische Astro-Komponente
│ │ ├── Footer.astro
│ │ ├── NavBar.tsx # React-Insel
│ │ └── Counter.svelte # Svelte-Insel
│ ├── content/ # Quellendateien der Content Collections
│ │ ├── blog/
│ │ │ ├── post-one.md
│ │ │ └── post-two.mdx
│ │ └── authors/
│ │ └── jane.json
│ ├── content.config.ts # Sammlungsschemas (Astro 5)
│ ├── middleware.ts # Request/Response-Middleware
│ ├── styles/
│ │ └── global.css
│ └── lib/ # Gemeinsame Hilfsprogramme
│ ├── utils.ts
│ └── constants.ts
└── .env # Umgebungsvariablen
Kurzreferenz View Transitions
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
Übergangsdirektiven
<!-- Element seitenübergreifend beibehalten (behält Zustand, vermeidet Neu-Rendering) -->
<audio transition:persist id="player">
<source src="/music.mp3" />
</audio>
<!-- Benannte Transition für Animationspaarung -->
<img transition:name="hero" src={post.heroImage} />
<!-- Benutzerdefinierte Animation -->
<div transition:animate="slide">Inhalt</div>
<div transition:animate="fade">Inhalt</div>
<div transition:animate="none">Keine Animation</div>
<!-- Mit Name beibehalten (für mehrere persistente Elemente) -->
<video transition:persist="media-player" />
Lifecycle-Ereignisse
<script>
document.addEventListener('astro:before-preparation', (e) => {
// Bevor die neue Seite abgerufen wird - Navigation abbrechen, Ladeanimation anzeigen
});
document.addEventListener('astro:after-preparation', (e) => {
// Neue Seite abgerufen, vor dem Austausch
});
document.addEventListener('astro:before-swap', (e) => {
// DOM-Austauschverhalten anpassen
});
document.addEventListener('astro:after-swap', () => {
// DOM aktualisiert - Skripte neu initialisieren
});
document.addEventListener('astro:page-load', () => {
// Seite vollständig geladen (wird bei der ersten und jeder Navigation ausgelöst)
// Dies anstelle von DOMContentLoaded mit View Transitions verwenden
});
</script>
Zurück/Vorwärts-Handhabung
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true, // Alle Links beim Hover vorabrufen
defaultStrategy: 'hover', // 'hover' | 'tap' | 'viewport' | 'load'
},
});
<!-- Vorabrufsteuerung pro Link -->
<a href="/about" data-astro-prefetch>Vorabruf bei Hover (Standard)</a>
<a href="/blog" data-astro-prefetch="viewport">Vorabruf, wenn sichtbar</a>
<a href="/contact" data-astro-prefetch="load">Sofortiger Vorabruf</a>
<a href="/external" data-astro-prefetch="false">Kein Vorabruf</a>
Entscheidungsbaum für das Deployment
Wo deployen?
│
├─ Wird Edge Computing + Cloudflare-Ökosystem benötigt (KV, D1, R2)?
│ └─ Cloudflare Pages/Workers
│ Adapter: @astrojs/cloudflare
│ Am besten für: Globales Edge, Workers-Bindings, kosteneffizient
│
├─ Wird Serverless + Vercel-Ökosystem benötigt (ISR, Analytics)?
│ └─ Vercel
│ Adapter: @astrojs/vercel
│ Am besten für: Next.js-Migration, Bildoptimierung, ISR
│
├─ Wird Serverless + Netlify-Ökosystem benötigt (Formulare, Identity)?
│ └─ Netlify
│ Adapter: @astrojs/netlify
│ Am besten für: JAMstack, eingebaute Formulare, Split-Testing
│
├─ Wird vollständige Serverkontrolle benötigt (Docker, benutzerdefinierte Runtime)?
│ └─ Node.js (eigenständig oder Express/Fastify)
│ Adapter: @astrojs/node
│ Am besten für: Selbst gehostet, WebSocket, langlebige Prozesse
│
└─ Reine statische Seite (kein SSR erforderlich)?
└─ Jeder statische Host (GitHub Pages, S3, Cloudflare Pages)
Kein Adapter erforderlich, output: 'static'
Am besten für: Blogs, Doks, Marketing-Websites
Adapter-Installation
# Cloudflare
npx astro add cloudflare
# Vercel
npx astro add vercel
# Netlify
npx astro add netlify
# Node.js
npx astro add node
Häufige Fehlerquellen
| Fehler | Warum | Lösung |
|---|---|---|
| Hydrationsfehler durch Nichtübereinstimmung | Server-HTML unterscheidet sich vom Client-Render (Daten, zufällige IDs, Browser-APIs) | client:only für browserabhängige Komponenten verwenden oder deterministisches Rendering sicherstellen |
import.meta.env im Client undefiniert | Nur PUBLIC_-Präfix-Variablen sind für clientseitigen Code verfügbar | In PUBLIC_MY_VAR umbenennen oder über Props vom Server übergeben |
| Dynamische Routen 404 bei SSG | getStaticPaths() gibt nicht alle möglichen Parameter zurück | Sicherstellen, dass getStaticPaths() jeden gültigen Pfad zurückgibt, oder auf Hybrid/SSR umstellen |
| Bilder werden nicht optimiert | Verwendung von <img> anstelle der Astro-Komponente <Image /> | Aus astro:assets importieren: import { Image } from 'astro:assets' und lokale Imports für src verwenden |
| SSR schlägt ohne Adapter fehl | output: 'server' oder 'hybrid' erfordert einen Deployment-Adapter | Adapter installieren: npx astro add cloudflare (oder vercel, netlify, node) |
| MDX-Komponenten werden nicht gerendert | Benutzerdefinierte Komponenten nicht an MDX-Inhalt übergeben | Komponenten über <Content components={{ MyComponent }} /> übergeben oder MDX-Konfiguration in astro.config.mjs verwenden |
| Schemaänderungen in Content Collections werden nicht übernommen | Typgenerierung ist zwischengespeichert, veraltete .astro-Typen | astro sync ausführen, um Typen neu zu generieren, Dev-Server neu starten |
client:* auf Astro-Komponenten | Client-Direktiven funktionieren nur auf Framework-Komponenten (React, Vue, Svelte) | Astro-Komponenten sind rein statisch; interaktive Teile in eine Framework-Komponente auslagern |
document / window ist nicht definiert | Serverseitiger Code kann nicht auf Browser-Globals zugreifen | Mit if (typeof window !== 'undefined') absichern oder in client:only verschieben |
| Styles leaken zwischen Komponenten | Verwendung von globalem CSS anstelle von bereichsbezogenen Styles | <style> (standardmäßig bereichsbezogen in .astro) oder <style is:global> gezielt verwenden |
| View Transitions unterbrechen Skripte | DOMContentLoaded feuert mit View Transitions nur einmal | astro:page-load-Ereignis verwenden, das bei jeder Navigation feuert |
| Umgebungsvariablen fehlen in Produktion | .env nicht geladen oder Plattform-Env-Vars nicht konfiguriert | envField in astro.config.mjs zur Validierung verwenden; Variablen im Platform-Dashboard setzen |
Sicherheitscheckliste für die Produktion
Für jedes Produktions-Deployment Folgendes beachten:
- CSP-Header - eine restriktive
Content-Security-Policykonfigurieren (siehe Middleware-Muster inreferences/deployment.md) - Remote-Bildbeschränkungen - explizite
image.domains/remotePatterns-Whitelists erzwingen; Bild-URLs niemals aus Benutzereingaben ableiten (SSRF-Risiko) - Host-Header-Validierung - überprüfen, ob der Request-Host mit den erwarteten Domains übereinstimmt (nur SSR/Hybrid)
- Geheimnisverwaltung - bei Cloudflare Workers Bindings verwenden (
wrangler secret put), keine in den Code eingebackenen Env-Vars; andernfalls Plattform-Geheimnisspeicher verwenden - Nur HTTPS - sicherstellen, dass alle externen Ressourcen (Skripte, Bilder, Schriftarten) über HTTPS geladen werden
- Eingabevalidierung - alle Benutzereingaben in SSR-Kontexten bereinigen (Query-Parameter, Formulardaten, Cookies)
Referenzdateien
| Datei | Inhalt | Zeilen |
|---|---|---|
references/content-collections.md | Schema-Muster, Zod-Typen, Abfragen, MDX, Content-Layer-API, Migrationen | ~500 |
references/islands-rendering.md | Erweiterte Hydrations-Muster, Server-Inseln, Leistungsoptimierung, Framework-Besonderheiten | ~400 |
references/view-transitions.md | Erweiterte Übergangsanimationen, Lifecycle-Hooks, benutzerdefinierte Verhaltensweisen zur Barrierefreiheit | ~300 |
references/deployment.md | Konfigurationen für einzelne Adapter, CSP-Middleware, Build-Optimierung, Env-Validierung | ~600 |
references/seo.md | Metadaten, strukturierte Daten, Open Graph, Sitemaps, RSS-Feeds | ~350 |
Zugehörige Skills
- astro-seo - SEO, strukturierte Daten, Sitemaps, RSS/Atom-Feeds (lokalisiert in
references/seo.md) - web-performance - Metriken (Web Vitals, LCP, TBT), Caching-Strategien, Lazy Loading, Asset-Optimierung
- responsive-tailwind - Responsive Design, Utility-First-CSS, mobile Navigation, Medienabfragen, Adaptive Layouts
- cloudflare-deployment - Cloudflare Workers-Seiten, KV, D1, R2, Bindings, CI/CD
Skill ID: 0xDarkMatter/claude-mods