Zum Inhalt springen
CASOON

Astro v6 Template: Von Grund auf neu gebaut – was sich gegenüber v5 ändert

Weniger Abhängigkeiten, schärfere Architektur, Astro-6-native – unser neues Open-Source-Starter-Template

16 Minuten
Astro v6 Template: Von Grund auf neu gebaut – was sich gegenüber v5 ändert
#Astro #Astro 6 #Template #Monorepo

Im vorherigen Artikel ging es um die Theorie: Was ändert sich in Astro 6, welche APIs fallen weg, wie migriert man. Jetzt geht es um die Praxis.

Wir haben unser Open-Source-Starter-Template komplett neu gebaut – nicht migriert, nicht gepatcht, sondern von einer leeren package.json aus. Das Ergebnis ist das astro-v6-template, der Nachfolger unseres astro-v5-template.

Warum Neuaufbau statt Migration?

Das v5-Template war über Monate gewachsen. 3 Apps, 16 Font-Pakete, ein Linting-Hybrid aus drei Tools, eine externe Design-System-Abhängigkeit und diverse Showcase-Features wie Leaflet-Maps, Glassmorphism-Demos und Animationsseiten. Alles funktional, aber überladen.

Migration hätte bedeutet: Zod 3 auf 4 aktualisieren, Legacy Collections umstellen, das Linting bereinigen, die Demo-App ausmisten – und trotzdem hätten wir die gewachsene Komplexität mitgeschleppt.

Der Neuaufbau hatte ein Ziel: Alles raus, was nicht direkt einem produktiven Projekt dient. Was bleibt, nutzt Astro 6 nativ, nicht drumherum.

Was sich geändert hat

Bereichv5v6
Apps3 (blank, base, demo)2 (starter, blog)
Font-Pakete163
LintingBiome + ESLint + PrettierBiome only
Design System@casoon/atlas-styles + CustomEigene CSS Tokens
Node.js>= 20>= 22.12.0
Zodv3v4
Content CollectionsLegacy APILoader API
View Transitions<ViewTransitions /><ClientRouter />
FormulareAPI-Route + manuelles ParsingAstro Actions + Zod
CSRF-SchutzKeinercheckOrigin
DeploymentStatischCloudflare Workers (SSR)

Die Tabelle sieht nach weniger aus. Ist es auch – und das ist der Punkt.

Zwei Apps statt drei

v5 hatte blank (zu leer), base (die meisten nahmen diese) und demo (zu voll). In der Praxis wurde fast immer base geklont und angepasst. blank war ein leeres Versprechen, demo eine Spielwiese, die keiner in Produktion übernahm.

v6 hat zwei Apps, die jeweils einen konkreten Use Case abdecken:

Starter ist eine Landing Page mit Hero-Sektion, Feature-Grid, Kontaktformular und einer API-Route für die Formularverarbeitung. Alles, was man für eine Firmenseite oder ein Projektlanding braucht – nichts, was man erst rauslöschen muss.

Blog ist ein Blog-Template mit MDX-Support, Content Collections über die neue Loader API, Tag-Anzeige und automatischem RSS-Feed. Kein CMS, kein Admin-Panel – einfach Markdown-Dateien, die als Blog gerendert werden.

Beide Apps sind eigenständig deploybar und teilen sich die Shared Packages für Styles, UI-Komponenten und Utilities.

Drei Fonts statt sechzehn

Das v5-Template hatte 16 Font-Pakete: Inter, Archivo, Crimson Text, DM Sans, Fira Code, JetBrains Mono, Lora, Manrope, Merriweather, Montserrat, Playfair Display, Poppins, Raleway, Source Serif 4, Space Grotesk, Work Sans. Das war als Auswahl gedacht, in der Praxis aber Ballast – die meisten Projekte nutzten zwei oder drei davon.

v6 hat drei: Inter für UI und Fließtext, Lora für Lesetext und Hervorhebungen, Fira Code für Code-Blöcke. Wer andere Fonts braucht, tauscht sie aus. Ein Template soll keine Font-Bibliothek sein.

Biome statt drei Tools

Das war die Änderung, auf die wir am längsten gewartet haben.

v5 hatte Biome für JavaScript und TypeScript, ESLint für .astro-Dateien (weil Biome das damals nicht konnte) und Prettier für die Formatierung von Astro, Svelte und CSS. Drei Tools, fünf Konfigurationsdateien, verschiedene Regeln für verschiedene Dateitypen. Es funktionierte, war aber fragil.

Biome 2.x kann inzwischen .astro, .svelte und .css verarbeiten. Damit entfallen ESLint und Prettier komplett. Eine biome.json, ein Befehl (pnpm check), alle Dateitypen. Kein „welches Tool ist gerade für welche Datei zuständig?” mehr.

Design Tokens: OKLCH statt Hex

Das Style-System basiert auf CSS Custom Properties mit OKLCH-Farbwerten. OKLCH ist perceptually uniform – gleiche Lightness-Werte wirken tatsächlich gleich hell, anders als bei Hex oder HSL. Das macht konsistente Farbskalen deutlich einfacher.

Das Token-System ist in drei Ebenen aufgebaut: Rohwerte (die eigentlichen OKLCH-Farben), semantische Tokens (was ist Hintergrund, Text, Akzent?) und Komponenten-Tokens (Nav-Höhe, Card-Radius, Button-Radius). Dark Mode tauscht nur die semantische Ebene – die Komponenten bleiben gleich.

Im v5-Template kam das Design-System teilweise aus @casoon/atlas-styles, einer externen Dependency. Das war sinnvoll, als atlas als eigenständiges Projekt geplant war. Für ein Starter-Template erzeugt es aber eine Abhängigkeit, die man nicht kontrolliert. v6 hat eigene, schlanke Tokens – direkt im Repo, direkt anpassbar.

Vier UI-Komponenten statt fünfzehn

v5 hatte über 15 Komponenten im Shared-UI-Paket: Card, BlogCard, GlassCard, Hero, Modal, Toast, Newsletter, ContactForm, ErrorBoundary, OptimizedImage, ThemeToggle und diverse SEO-Varianten. Die Idee war gut – fertige Bausteine für schnellen Start. In der Praxis wurden die meisten Komponenten aber sofort angepasst oder ersetzt.

v6 hat vier Komponenten, die tatsächlich shared sind:

  • BaseLayout – HTML-Grundstruktur mit Skip-Link, Dark-Mode-Initialisierung und ClientRouter für View Transitions
  • Navbar – Responsive Navigation mit Slot für zusätzliche Elemente wie den Theme-Toggle
  • PageSEO – Meta-Tags, Open Graph, Twitter Cards und JSON-LD als eine Komponente
  • ThemeToggle – Dark Mode Switch als Svelte-5-Komponente mit der neuen Runes-API

Alles andere gehört in die Apps. Ein Template soll Architektur vorgeben, nicht Fertigkomponenten liefern, die man doch anpasst.

Content Collections: Die neue Art

Die Content Collections nutzen die Loader API – der größte Breaking Change in Astro 6. Statt type: 'content' definiert man jetzt einen glob() Loader, der explizit sagt, wo die Dateien liegen und welche Patterns geladen werden. Die Config-Datei wandert von src/content/config.ts eine Ebene nach oben zu src/content.config.ts.

Zod kommt jetzt aus astro/zod statt aus astro:content, und die Schemas nutzen Zod-4-Syntax: z.email() statt z.string().email(), z.coerce.date() für flexiblere Datumsformate.

In den Seiten ändert sich die Render-API: render(post) als importierte Funktion statt post.render() als Methode, und post.id statt post.slug. Kleine Änderungen, die aber jede Datei betreffen, die mit Collections arbeitet.

View Transitions mit ClientRouter

Das BaseLayout integriert den neuen <ClientRouter /> – den Nachfolger von <ViewTransitions />. Damit navigiert die Seite zwischen Routen ohne Full Page Reload. Das funktioniert out of the box für beide Apps.

Ein Detail, das man leicht übersieht: Der Dark-Mode-State muss bei View Transitions explizit erhalten bleiben. Das BaseLayout registriert dafür ein astro:after-swap Event, das den Theme-State nach jeder Navigation neu anwendet. Ohne das würde der Dark Mode bei jedem Seitenwechsel zurückspringen.

Zod v4 an drei Stellen

Das Template nutzt Zod v4 nicht nur in den Content Collections, sondern auch für Environment-Validierung und API-Requests.

Environment: Jede App hat eine env.ts, die import.meta.env gegen ein Zod-Schema validiert. Fehlende oder falsche Umgebungsvariablen werden beim Start erkannt, nicht erst im Produktionsbetrieb.

API-Routes: Das Kontaktformular im Starter validiert Eingaben serverseitig mit einem Zod-Schema. Schema-Definition, Validierungslogik und Response-Helper liegen im shared Utils-Paket – die API-Route selbst ist ein Einzeiler.

Shared Utils: Eine cn()-Utility-Funktion kombiniert clsx und tailwind-merge für bedingte Tailwind-Klassen ohne Konflikte. Klein, aber in fast jeder Komponente nützlich.

Astro Actions statt API-Routes

Das Kontaktformular im Starter nutzt Astro Actions – den neuen Weg, serverseitige Logik direkt an Formulare zu binden. Statt einer separaten API-Route definiert man eine Action mit Zod-Schema, und Astro übernimmt Validierung, Fehlerhandling und Typsicherheit.

Das Formular funktioniert ohne JavaScript: method="POST" und action={actions.contact} reichen. Validierungsfehler kommen feldgenau zurück, Erfolgsmeldungen ebenso. Progressive Enhancement, wie es sein sollte – kein Client-Framework nötig, kein fetch-Boilerplate, kein manuelles Error-Mapping.

Prefetch: Alles vorladen, aber intelligent

Beide Apps nutzen Astros Prefetch mit der Viewport-Strategie: Alle Links werden vorgeladen, aber erst, wenn sie ins Sichtfeld scrollen. Das spart Bandbreite gegenüber eagerer Strategien und fühlt sich trotzdem an wie eine SPA – Seitenwechsel sind praktisch instant.

Die Konfiguration ist drei Zeilen in der Astro-Config. Keine Komponenten-Anpassung nötig, kein manuelles Markieren einzelner Links.

Security: checkOrigin

Das Template aktiviert checkOrigin in der Astro-Config. Damit prüft Astro bei POST-Requests automatisch den Origin-Header – CSRF-Schutz auf Framework-Ebene, ohne eigene Middleware, ohne Token-Management. Das greift für klassische Formulare und Astro Actions gleichermaßen.

Cloudflare Workers: Edge Deployment

Die Starter-App läuft auf Cloudflare Workers – nicht als statischer Export, sondern als vollwertige SSR-Anwendung. Der @astrojs/cloudflare-Adapter macht Astro Actions, serverseitige Formulare und dynamische Routen am Edge möglich.

Die wrangler.toml konfiguriert den Worker mit Node.js-Kompatibilität, statischen Assets über Cloudflares Asset-Pipeline und Observability für Monitoring. Deployment ist ein Befehl: pnpm deploy baut und deployt in einem Schritt.

pnpm Catalog: Eine Stelle für alle Versionen

Alle Abhängigkeiten werden im pnpm Catalog in der pnpm-workspace.yaml zentral verwaltet. In den einzelnen package.json-Dateien steht nur noch "astro": "catalog:" statt einer konkreten Version. Bei einem Update ändert sich eine Zeile an einer Stelle, nicht fünf Zeilen in fünf Dateien.

Das verhindert Version Drift – das Problem, dass verschiedene Apps im Monorepo versehentlich unterschiedliche Versionen derselben Dependency nutzen. In v5 musste man das manuell prüfen. In v6 ist es strukturell ausgeschlossen.

CI: Einfach genug, dass man sie nicht umgeht

Die v5-CI hatte einen Quality-Job und einen Matrix-Build über drei Apps. v6 hat einen Job mit drei Schritten: Biome Check, Type Check, Build. Keine Matrix, kein Artifact-Upload, keine parallelen Stages.

Ein nettes Detail: Nach jedem Push auf main aktualisiert die CI automatisch die Versions-Badges in der README. Die Versionsnummern in den Badges stimmen damit immer mit den tatsächlichen Abhängigkeiten überein.

Accessibility als Grundlage

Das Template setzt WCAG 2.1 Level AA als Baseline. Das klingt nach Checkbox, steckt aber in konkreten Entscheidungen:

  • Skip-Link im BaseLayout für Keyboard-Navigation
  • Farbkontraste mindestens 4.5 zu 1 durch die OKLCH-Token-Auswahl
  • aria-label auf interaktiven Elementen wie dem ThemeToggle
  • focus-visible Styles global definiert, nicht pro Komponente
  • Semantisches HTML durchgängig: nav, main, article, section

Das sind keine Features, die man aktiviert. Das ist die Grundstruktur, auf der alles aufbaut.

Was bewusst fehlt

Das Template hat eine explizite „Not Planned”-Liste:

  • Kein Demo-Showcase – Nur produktive Use Cases
  • Keine 16 Fonts – 3 reichen
  • Kein externes Design-System – Eigene, schlanke Tokens
  • Kein ESLint/Prettier – Biome deckt alles ab
  • Keine Map/Animation/Typografie-Demos – Fokus auf Kernfeatures

Das v5-Template war ein Kompendium: „Hier ist alles, was Astro kann.” Das v6-Template ist eine Meinung: „Hier ist, was du brauchst.”

i18n: Zweisprachig von Anfang an

Beide Apps unterstützen Englisch (default) und Deutsch über Astros i18n-Routing. Englische Seiten liegen im Root (/, /contact, /blog/welcome), deutsche unter dem Prefix /de/. Die Navbar enthält einen Language Switcher, der zwischen den Sprachversionen wechselt.

Jede App hat eigene Übersetzungsdateien in src/i18n/, die gemeinsame Locale-Logik liegt in @astro-v6/shared/utils/i18n. Das System ist schlank: kein i18n-Framework, keine Runtime-Bibliothek – Astros eigenes Routing reicht.

OG Images: Generiert, nicht designt

Open Graph Images werden zur Build-Zeit generiert – mit Satori für das Rendering und resvg für die PNG-Konvertierung. Kein Figma-Export, kein manuelles Erstellen pro Seite.

# Manuell generieren
pnpm --filter starter generate:og
pnpm --filter blog generate:og

# Automatisch vor jedem Build
pnpm build

Die Bilder landen in public/og/*.png (1200x630, gitignored). Das Blog-Script liest die MDX-Frontmatter und generiert postspezifische Images. Jede Seite referenziert ihr OG-Bild über <PageSEO ogImage={...}>.

Content Security Policy

Das Template nutzt Astros nativen CSP-Support mit SHA-256 Nonces. Inline-Scripts bekommen automatisch Nonce-Attribute, externe Ressourcen werden explizit whitegelistet. Das reduziert die Angriffsfläche für XSS deutlich – ohne manuelle Header-Konfiguration oder Middleware.

Sessions: Serverseitiger State am Edge

Die Starter-App demonstriert Astros neues Session-Feature mit Cloudflare KV als Backend. Serverseitiger State – etwa für Formulare, die über mehrere Schritte gehen, oder für temporäre Nutzerdaten – ohne eigene Datenbank, ohne Cookie-Workarounds. Die Session-Konfiguration ist Teil der Astro-Config, das API ist Astro.session.get() und Astro.session.set().

E2E Tests: Playwright mit axe-core

Beide Apps haben Playwright-Tests, die nicht nur Funktionalität prüfen, sondern auch Barrierefreiheit. axe-core scannt jede getestete Seite auf WCAG 2.1 AA Violations.

# Alle Tests
pnpm test:e2e

# Pro App
pnpm test:e2e:starter
pnpm test:e2e:blog

Die Tests decken Navigation, i18n-Wechsel, SEO- und OG-Meta-Tags, Kontaktformular, Theme-Toggle, RSS-Feed, robots.txt und Sitemap ab. Builds müssen vor dem Testlauf existieren – die Tests laufen gegen die statischen Artefakte.

Build Performance Metrics

Beide Apps messen ihre Build-Performance mit @casoon/astro-speed-measure. Die Integration trackt Integrations-Hooks, Vite-Plugin-Timing, Page-Rendering und Asset-Processing. Nach jedem Build gibt es einen Konsolenbericht und eine JSON-Baseline für Trendvergleiche.

// astro.config.mjs
import speedMeasure from '@casoon/astro-speed-measure';

export default defineConfig({
  integrations: [
    // ... andere Integrations
    speedMeasure(), // immer als letzte Integration
  ],
});

Unterstützt Budgets, HTML-Reports und GitHub Actions CI Summaries.

Wie du startest

git clone https://github.com/casoon/astro-v6-template.git
cd astro-v6-template
pnpm install
pnpm dev          # Starter auf Port 4321
pnpm dev:blog     # Blog auf Port 4322

Für ein neues Projekt legst du einen Ordner unter apps/ an, referenzierst die Shared Packages mit workspace:* und baust von dort aus weiter. Das Template ist darauf ausgelegt, erweitert zu werden – nicht als Einmalkopie, die man nach dem Clone vergisst.

Unterm Strich

Migration wäre gegangen. Neuaufbau war besser – nicht weil v5 schlecht war, sondern weil Astro 6 genug ändert, um die Architektur grundsätzlich zu überdenken.

Das Ergebnis: Ein Monorepo mit zwei produktiven Apps, drei Shared Packages, einem Linting-Tool und einer zentralen Versionsverwaltung. Keine Showcase-Features, keine Abhängigkeiten, die man nicht braucht, keine Konfigurationsdateien, die niemand versteht.

Wer Astro 6 evaluiert, hat damit einen produktionsnahen Ausgangspunkt. Wer von v5 kommt, sieht im direkten Vergleich, was sich lohnt zu übernehmen. Alle Features und die Technologie-Übersicht sind auf der astro-v6-template Landingpage zusammengefasst.