Astro 6 Architektur: Islands, Server Islands & Content Layer API im Detail
Technischer Deep-Dive in Astro 6: Islands Architecture, Server Islands für dynamische Inhalte, die neue Content Layer API und View Transitions mit ClientRouter.
Rechtliches & Info
TypeScript strict vollständig in Astro-Projekten nutzen: das strict-Flag, der satisfies-Operator, typsichere Props mit Generics und das Typsystem der Content Collections.
TypeScript mit strict: true in einem Astro-Projekt zu betreiben bedeutet mehr als das Flag in tsconfig.json zu setzen — es verändert, wie man Props definiert, wie man Content-Collections typisiert und wo man satisfies statt as verwendet. Dieser Artikel zeigt die wichtigsten Patterns für produktionsreife Astro-Projekte.
strict: true in der tsconfig.json ist ein Sammelflag, das mehrere TypeScript-Checks gleichzeitig aktiviert:
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
Die wichtigsten enthaltenen Checks:
| Flag | Effekt |
|---|---|
strictNullChecks | null und undefined sind keine gültigen Werte für andere Typen |
strictFunctionTypes | Funktionsparameter werden contravariant geprüft |
noImplicitAny | Implizites any ist verboten |
strictPropertyInitialization | Klassen-Properties müssen im Konstruktor initialisiert werden |
Zusätzlich empfehlenswert: noUncheckedIndexedAccess — Array-Zugriffe per Index geben T | undefined zurück, nicht T.
const items = ['Astro', 'React', 'TypeScript'];
const first = items[0];
// ^? string | undefined (mit noUncheckedIndexedAccess)
// ^? string (ohne)
// Zwingt zu explizitem Null-Check:
if (first !== undefined) {
console.log(first.toUpperCase());
}
Astro-Komponenten nutzen interface Props oder type Props im Frontmatter-Block. Mit strict mode muss man präzise sein.
---
interface Props {
title: string;
description?: string;
level?: 1 | 2 | 3 | 4 | 5 | 6;
class?: string;
}
const {
title,
description,
level = 2,
class: className = '',
} = Astro.props;
const HeadingTag = `h${level}` as `h${typeof level}`;
---
<HeadingTag class={className}>
{title}
{description && <span class="text-sm font-normal text-gray-500">{description}</span>}
</HeadingTag>
---
type ButtonProps =
| {
as: 'button';
type?: 'button' | 'submit' | 'reset';
onClick?: () => void;
href?: never;
}
| {
as: 'a';
href: string;
type?: never;
onClick?: never;
};
type Props = ButtonProps & {
children: string;
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
class?: string;
};
const { as: Tag = 'button', variant = 'primary', size = 'md', class: className = '', ...rest } = Astro.props;
---
Discriminated Unions erzwingen, dass href nur bei as="a" gültig ist und type nur bei as="button" — TypeScript fängt Fehler zur Compile-Zeit.
satisfies wurde in TypeScript 4.9 eingeführt und löst ein häufiges Problem: Man möchte, dass ein Objekt einem bestimmten Typ entspricht, aber TypeScript soll den spezifischsten Typen inferieren, nicht den allgemeinen.
// Ohne satisfies: TypeScript inferiert Record<string, string>
// und verliert die Information über die exakten Keys
const categoryColors = {
BFSG: 'bg-purple-100 text-purple-700',
SEO: 'bg-green-100 text-green-700',
Webdesign: 'bg-blue-100 text-blue-700',
} as const;
// Mit satisfies: TypeScript prüft gegen den Record-Typ,
// behält aber die literalen Key-Namen
type Category = 'BFSG' | 'SEO' | 'Webdesign' | 'Performance' | 'News';
const categoryColors = {
BFSG: 'bg-purple-100 text-purple-700',
SEO: 'bg-green-100 text-green-700',
Webdesign: 'bg-blue-100 text-blue-700',
Performance: 'bg-orange-100 text-orange-700',
News: 'bg-gray-100 text-gray-700',
} satisfies Record<Category, string>;
// TypeScript weiß jetzt:
// categoryColors.BFSG → 'bg-purple-100 text-purple-700' (literal type)
// categoryColors['Unbekannt'] → Fehler: Key nicht im Typ
Im Gegensatz zu as Record<Category, string> verhindert satisfies, dass fehlerhafte Werte stillschweigend akzeptiert werden.
// data/blog-articles.ts
export function getArticlesByCategory<T extends BlogArticle['category']>(
category: T
): BlogArticle[] {
return blogArticles.filter((a) => a.category === category);
}
// Stärker typisiert: gibt immer ein Array zurück, nie undefined
export function getArticleBySlug(slug: string): BlogArticle | undefined {
return blogArticles.find((a) => a.slug === slug);
}
// Mit noUncheckedIndexedAccess korrekt:
export function getLatestArticle(): BlogArticle | undefined {
const sorted = getPublishedArticles();
return sorted[0]; // gibt BlogArticle | undefined zurück
}
Astro Content Collections mit defineCollection und Zod liefern vollständige Typsicherheit:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string().min(10).max(80),
description: z.string().min(50).max(160),
pubDate: z.date(),
category: z.enum(['Webdesign', 'SEO', 'Performance', 'BFSG', 'News']),
tags: z.array(z.string()).min(1).max(10),
readingTime: z.number().int().positive(),
featured: z.boolean().optional().default(false),
ogImage: z.string().optional(),
}),
});
Das Zod-Schema wird zur Laufzeit validiert — fehlerhafte Frontmatter-Felder brechen den Build mit einem klaren Fehler ab, nicht mit einem stillen undefined im Template.
import type { CollectionEntry } from 'astro:content';
// Inferierter Typ für einen Blog-Eintrag
type BlogEntry = CollectionEntry<'blog'>;
// Utility für Props einer Artikel-Seite
interface ArticlePageProps {
entry: BlogEntry;
}
Damit ist der Typ des entry-Objekts vollständig aus dem Schema inferiert — kein manuelles Interface nötig, das vom Schema abweichen könnte.
1. Astro.props ohne explizites Interface
---
// FEHLER mit strict mode: implizites any
const { title } = Astro.props;
// RICHTIG:
interface Props { title: string }
const { title } = Astro.props;
---
2. Array-Zugriff bei getStaticPaths
---
export async function getStaticPaths() {
const articles = await getCollection('blog');
return articles.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
// TypeScript weiß, dass entry vom Typ CollectionEntry<'blog'> ist
const { entry } = Astro.props;
const { Content } = await render(entry);
---
3. Optional Chaining vs. Non-null Assertion
// Non-null Assertion (!) ist mit strict mode erlaubt, aber riskant
const article = getArticleBySlug(slug)!; // Throws wenn undefined
// Besser: Optional Chaining mit explizitem Fehler-Handling
const article = getArticleBySlug(slug);
if (!article) {
return Astro.redirect('/blog/', 301);
}
// Ab hier ist article sicher BlogArticle
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"],
"@data/*": ["src/data/*"],
"@layouts/*": ["src/layouts/*"],
"@styles/*": ["src/styles/*"]
},
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true
},
"include": ["src", ".astro"]
}
verbatimModuleSyntax stellt sicher, dass import type für reine Type-Imports verwendet wird — wichtig für Tree-Shaking und korrekte ESM-Ausgabe.
TypeScript-Architektur für Ihr Projekt? Wender Media baut typsichere Frontend-Stacks, die auch in 12 Monaten noch wartbar sind — info@wendermedia.info.
Themen:
Wender Media unterstützt Sie bei der praktischen Umsetzung — von der technischen Konzeption bis zum Launch. Schreiben Sie uns, wir antworten innerhalb von 24 Stunden.
Jetzt Beratung anfragenKostenlos & unverbindlich — info@wendermedia.info
Technischer Deep-Dive in Astro 6: Islands Architecture, Server Islands für dynamische Inhalte, die neue Content Layer API und View Transitions mit ClientRouter.
Was sich mit Tailwind CSS 4 wirklich ändert: CSS-first Konfiguration mit @theme, der Oxide Compiler, Container Queries, color-mix() und die Migration von v3.
Die Atomic Design Methodik konsequent in Astro umsetzen: Hierarchie-Regeln, Import-Grenzen, Slot-Komposition und häufige Fallstricke beim Aufbau eines Komponentensystems.