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
Netlify Functions produktiv einsetzen: Runtime-Eigenheiten, Secrets-Management, Observability mit Netlify Logs und praktische Integrationen mit Brevo und Stripe.
Netlify Functions sind das bevorzugte Backend-Werkzeug für Astro-Projekte auf Netlify, wenn man keinen vollständigen Server-Stack betreiben will. Sie laufen als AWS Lambda-Functions im Hintergrund, integrieren sich nahtlos in den Deploy-Workflow und haben nach der Einrichtung wenig Betriebsaufwand. Dieser Artikel zeigt, worauf man in der Praxis achten muss.
Netlify unterscheidet drei Function-Typen:
| Typ | Pfad | Auslöser |
|---|---|---|
| Synchrone Functions | netlify/functions/ | HTTP-Request an /.netlify/functions/<name> |
| Background Functions | netlify/functions/ (mit -background Suffix) | HTTP-Request, aber asynchron — kein Response erwartet |
| Edge Functions | netlify/edge-functions/ | HTTP-Request, läuft am Edge-Node |
Für die meisten Anwendungsfälle — Formulare, E-Mail-Versand, Webhook-Handler — reichen synchrone Functions.
netlify/
└── functions/
├── contact.ts # Synchron: POST /kontakt
├── newsletter.ts # Synchron: POST /newsletter/subscribe
└── stripe-webhook.ts # Synchron: POST /stripe/webhook
// netlify/functions/contact.ts
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
export const handler: Handler = async (event: HandlerEvent, _context: HandlerContext) => {
// CORS-Preflight
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 204,
headers: corsHeaders(),
};
}
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}
const body = JSON.parse(event.body ?? '{}');
// Eingabe validieren
if (!isValidContactForm(body)) {
return {
statusCode: 400,
headers: corsHeaders(),
body: JSON.stringify({ error: 'Ungültige Eingabe' }),
};
}
try {
await sendContactEmail(body);
return {
statusCode: 200,
headers: { ...corsHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({ success: true }),
};
} catch (err) {
console.error('Fehler beim E-Mail-Versand:', err);
return {
statusCode: 500,
headers: corsHeaders(),
body: JSON.stringify({ error: 'Interner Fehler' }),
};
}
};
function corsHeaders() {
return {
'Access-Control-Allow-Origin': process.env.SITE_URL ?? '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};
}
Umgebungsvariablen werden in der Netlify-UI unter “Site configuration > Environment variables” gesetzt. Sie sind im Function-Code über process.env verfügbar.
Wichtige Regeln:
.env.local (in .gitignore eingetragen).env im Repository// Secrets aus process.env lesen
const brevoApiKey = process.env.BREVO_API_KEY;
const stripeSecret = process.env.STRIPE_SECRET_KEY;
if (!brevoApiKey) {
throw new Error('BREVO_API_KEY fehlt in den Umgebungsvariablen');
}
Für lokale Entwicklung mit netlify dev:
# .env.local (gitignored)
BREVO_API_KEY=xkeysib-...
STRIPE_SECRET_KEY=sk_test_...
SITE_URL=http://localhost:4321
# Netlify CLI startet Function-Runtime + Astro-Dev-Server
netlify dev
// netlify/functions/newsletter.ts
import type { Handler } from '@netlify/functions';
interface BrevoContact {
email: string;
listIds: number[];
updateEnabled: boolean;
attributes?: Record<string, string>;
}
export const handler: Handler = async (event) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}
const { email } = JSON.parse(event.body ?? '{}');
if (!email || !isValidEmail(email)) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Gültige E-Mail erforderlich' }),
};
}
const contact: BrevoContact = {
email,
listIds: [Number(process.env.BREVO_LIST_ID)],
updateEnabled: false,
attributes: {
OPT_IN_SOURCE: 'wendermedia.info',
},
};
const response = await fetch('https://api.brevo.com/v3/contacts', {
method: 'POST',
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'api-key': process.env.BREVO_API_KEY!,
},
body: JSON.stringify(contact),
});
if (response.status === 204 || response.status === 201) {
return {
statusCode: 200,
body: JSON.stringify({ success: true }),
};
}
// Bereits registriert ist kein Fehler aus Nutzersicht
if (response.status === 400) {
const data = await response.json();
if (data.code === 'duplicate_parameter') {
return { statusCode: 200, body: JSON.stringify({ success: true, alreadySubscribed: true }) };
}
}
console.error('Brevo API Fehler:', response.status, await response.text());
return { statusCode: 500, body: JSON.stringify({ error: 'Fehler beim Eintragen' }) };
};
function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Stripe sendet Webhooks für Zahlungsereignisse. Die Signatur muss validiert werden — ohne Validierung kann jeder beliebige Daten an den Endpunkt senden.
// netlify/functions/stripe-webhook.ts
import Stripe from 'stripe';
import type { Handler } from '@netlify/functions';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export const handler: Handler = async (event) => {
const sig = event.headers['stripe-signature'];
if (!sig || !event.body) {
return { statusCode: 400, body: 'Fehlende Signatur oder Body' };
}
let stripeEvent: Stripe.Event;
try {
stripeEvent = stripe.webhooks.constructEvent(event.body, sig, webhookSecret);
} catch (err) {
console.error('Ungültige Stripe-Signatur:', err);
return { statusCode: 400, body: 'Ungültige Signatur' };
}
switch (stripeEvent.type) {
case 'payment_intent.succeeded': {
const paymentIntent = stripeEvent.data.object as Stripe.PaymentIntent;
await fulfillOrder(paymentIntent);
break;
}
case 'customer.subscription.deleted': {
const subscription = stripeEvent.data.object as Stripe.Subscription;
await cancelSubscription(subscription);
break;
}
}
return { statusCode: 200, body: JSON.stringify({ received: true }) };
};
Wichtig für Stripe-Webhooks: Netlify muss den rohen Request-Body empfangen, nicht einen geparsten JSON. Der @netlify/functions-Handler liefert event.body als String — perfekt für die Signatur-Validierung.
Netlify stellt Function-Logs in der Web-UI unter “Functions” bereit. Für strukturiertes Logging:
function log(level: 'info' | 'warn' | 'error', message: string, data?: unknown) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...(data ? { data } : {}),
};
console[level](JSON.stringify(entry));
}
Strukturierte JSON-Logs sind leichter nach Fehlern zu filtern als freie Strings. Netlify zeigt die letzten 100 Einträge im UI — für tiefere Analyse empfiehlt sich ein Log-Aggregator (DataDog, Axiom, Logtail).
Netlify Functions haben kein eingebautes Rate Limiting. Für öffentliche Endpunkte (Formulare, Newsletter) sollte man mindestens ein einfaches IP-basiertes Throttling implementieren:
// Einfacher In-Memory-Cache — bei Lambda-Instanz-Neustart zurückgesetzt
const requestCounts = new Map<string, { count: number; resetAt: number }>();
function isRateLimited(ip: string, maxRequests = 5, windowMs = 60_000): boolean {
const now = Date.now();
const entry = requestCounts.get(ip);
if (!entry || entry.resetAt < now) {
requestCounts.set(ip, { count: 1, resetAt: now + windowMs });
return false;
}
if (entry.count >= maxRequests) return true;
entry.count++;
return false;
}
Für produktionsreifes Rate Limiting bei hohem Traffic: Netlify Edge Functions mit Key-Value-Store oder ein dedizierter Rate-Limiting-Service (Upstash Redis).
Serverless Backend für Ihr Astro-Projekt? Wender Media richtet Netlify Functions, Webhooks und API-Integrationen professionell ein — 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.
TypeScript strict vollständig in Astro-Projekten nutzen: das strict-Flag, der satisfies-Operator, typsichere Props mit Generics und das Typsystem der Content Collections.