Route Handler
Wenn etwas von außen zugreifen soll
Eine route.ts antwortet auf eine Adresse mit Daten statt mit einer Seite. Gedacht für Zugriffe von außen – nicht für deine eigene Oberfläche.
Lesen macht die Server-Komponente, Schreiben die Server Action. Bleibt eine Lücke: Was, wenn ein fremdes System mit dir reden will? Dafür gibt es den Route HandlerEine `route.ts`, die auf eine Adresse antwortet, ohne eine Seite zu sein – der Weg zu einer eigenen API.→.
export async function GET() {
const artikel = await db.artikel.findMany();
return Response.json(artikel);
}
export async function POST(request: Request) {
const daten = await request.json();
const neu = await db.artikel.create({ data: daten });
return Response.json(neu, { status: 201 });
} In einem Ordner darf nur eines von beidem liegen. Eine Adresse ist entweder eine Seite oder ein Endpunkt. Deshalb legt man Handler üblicherweise unter app/api/… ab.
Wofür wirklich
- +
Webhooks: Zahlungsanbieter, Redaktionssystem, GitHub melden sich bei dir
- +
Eine öffentliche Schnittstelle für fremde Systeme oder eine mobile App
- +
Dateien ausliefern: Bilder erzeugen, PDF, CSV-Export
- +
OAuth-Rückleitungen und ähnliche Umleitungen
- −
Die eigene Server-Komponente ruft die eigene Route auf – ein Netzwerkumweg zu sich selbst
- −
Formulare der eigenen Oberfläche – dafür sind Server Actions kürzer und sicherer
- −
Daten für die eigene Client-Komponente, die auch von oben hereingereicht werden könnten
Server Actions sind die Haustür für die eigene Familie – kurz, direkt, ohne Formalitäten. Ein Route Handler ist der Lieferanteneingang: dokumentiert, mit Adresse, für Leute von außerhalb.
Was drin steckt
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const q = searchParams.get("q") ?? "";
if (!q) {
return Response.json({ fehler: "q fehlt" }, { status: 400 });
}
const treffer = await db.artikel.findMany({
where: { titel: { contains: q } },
});
return Response.json(treffer);
} export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params; // auch hier: erst awaiten
await db.artikel.delete({ where: { id } });
return new Response(null, { status: 204 });
} Anders als bei einer Seite steht hier kein Layout und keine Prüfung davor. Wer die Adresse kennt, kann sie aufrufen. Jeder Handler prüft also selbst: Ist ein gültiger Schlüssel dabei? Stimmt die Signatur des Webhooks? Darf diese Person das?
Zwischenspeichern
// GET-Handler sind standardmäßig dynamisch – jede Anfrage läuft durch.
// Willst du das Ergebnis zwischenspeichern:
export const revalidate = 60;
// Umgekehrt ausdrücklich immer frisch:
export const dynamic = "force-dynamic"; ▸ Genauer erklärt: ein Webhook mit Revalidierung optional
Der häufigste sinnvolle Einsatz: Ein Redaktionssystem meldet, dass etwas veröffentlicht wurde, und deine Seite räumt daraufhin den passenden Zwischenspeicher ab.
import { revalidateTag } from "next/cache";
export async function POST(request: Request) {
const schluessel = request.headers.get("x-webhook-key");
if (schluessel !== process.env.WEBHOOK_KEY) {
return new Response("Nicht erlaubt", { status: 401 });
}
const { typ } = await request.json();
revalidateTag(typ); // z. B. "artikel"
return Response.json({ ok: true });
} Ohne die erste Prüfung könnte jeder deine Seite beliebig oft neu bauen lassen. Der Schlüssel gehört in eine Umgebungsvariable ohne NEXT_PUBLIC_ – sonst landet er im Browser.
Sitzt das schon?
4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.