Was macht Sanity Patterns?
Kurzanleitung: Verwenden Sie Sanity für strukturiertes Content-Management mit GROQ-Abfragen, getypten Schemas via
defineType/defineFieldund@sanity/clientfür das Datenabrufen. Setzen SieapiVersionimmer auf einen datierten String, verwenden SieuseCdn: truefür öffentliche Lesezugriffe, behandeln Sie Entwurfsdokumente explizit, nutzen Sie@sanity/image-urlfür Bildtransformationen und rendern Sie Rich Text mit@portabletext/react. Generieren Sie TypeScript-Typen mitsanity typegen generate.
<critical_requirements>
KRITISCH: Vor der Nutzung dieses Skills
Der gesamte Code muss den Projektkonventionen in CLAUDE.md folgen (kebab-case, benannte Exporte, Import-Reihenfolge,
import type, benannte Konstanten)
(Sie MÜSSEN bei createClient immer apiVersion auf einen datierten String wie '2025-02-19' setzen – das Weglassen verwendet eine veraltete API, die möglicherweise bricht)
(Sie MÜSSEN useCdn: true für öffentliche Leseabfragen und useCdn: false für Abfragen mit Token oder bei Bedarf nach aktuellen Daten verwenden)
(Sie MÜSSEN parametrisierte GROQ-Abfragen ($param) für dynamische Werte verwenden – interpolieren Sie niemals Benutzereingaben in GROQ-Strings)
(Sie MÜSSEN Entwürfe explizit behandeln – Entwurfsdokumente haben _id mit dem Präfix drafts. und werden standardmäßig nicht mit perspective: 'published' zurückgegeben)
(Sie MÜSSEN defineQuery() aus groq verwenden und Abfragen benannten Variablen zuweisen, um die TypeGen-Typgenerierung zu ermöglichen)
</critical_requirements>
Auto-Erkennung: Sanity, sanity, @sanity/client, createClient, GROQ, groq, defineType, defineField, defineArrayMember, @sanity/image-url, urlFor, @portabletext/react, PortableText, portable text, block content, sanity.config, sanity.cli, typegen, sanity studio, content lake
Wann verwenden:
- Einrichten von
@sanity/clientmitcreateClientzum Datenabruf - Schreiben von GROQ-Abfragen (Filter, Projektionen, Joins, Sortierung, Slicing)
- Definieren von Inhaltsschemas mit
defineType,defineField,defineArrayMember - Rendern von Portable Text (Block-Content) mit
@portabletext/react - Generieren von Bild-URLs mit
@sanity/image-url(responsive Bilder, Zuschnitte, Hotspots) - Durchführen von Mutationen (create, patch, delete, Transaktionen)
- Einrichten von Echtzeit-Listenern mit
client.listen() - Generieren von TypeScript-Typen mit Sanity TypeGen
Wichtige behandelte Muster:
- Client-Einrichtung mit
createClientundapiVersion-Konfiguration - GROQ-Abfragesprache: Filter, Projektionen, Sortierung, Slicing, Joins, Referenzen
- Schema-Definitionen: Dokumententypen, Objekttypen, Arrays, Referenzen, Bilder
- Portable-Text-Rendering mit benutzerdefinierten Komponenten
- Image-URL-Builder mit responsiven Bildern und Transformationen
- Mutationen: create, createOrReplace, patch, delete, Transaktionen
- Echtzeit-Listener via
client.listen() - TypeGen für typsichere GROQ-Abfragen mit
defineQuery()
Wann NICHT verwenden:
- Nur-GraphQL-APIs (Sanity unterstützt primär GROQ; verwenden Sie ggf. das GraphQL-Skill)
- Direkter Datenbankzugriff (Sanity ist ein gehosteter Content Lake, keine Datenbank)
- Nicht-Sanity-CMS-Plattformen (verwenden Sie das entsprechende Skill für Ihr CMS)
Detaillierte Ressourcen:
- Für Entscheidungsrahmen und Kurzreferenztabellen siehe reference.md
Client & GROQ:
- examples/core.md – Client-Einrichtung, GROQ-Abfragen, Fehlerbehandlung, TypeGen
Schemas:
- examples/schemas.md – defineType, defineField, Dokumententypen, Objekttypen, Referenzen, Bilder
Rich Content:
- examples/rich-content.md – Portable-Text-Rendering, Image-URL-Builder, responsive Bilder
Mutationen & Echtzeit:
- examples/mutations.md – Create, Patch, Delete, Transaktionen, Echtzeit-Listener
Philosophie
Sanity ist eine strukturierte Content-Plattform, die auf einem Echtzeit-Content-Lake, GROQ (Graph-Relational Object Queries) als Abfragesprache und Sanity Studio als anpassbarer Bearbeitungsumgebung basiert.
Kernprinzipien:
- Strukturierter Content – Content wird durch Schemas (
defineType,defineField) definiert, die Form, Validierung und Bearbeitungs-UI beschreiben. Schemas sind Code, keine Konfigurationsdateien. - GROQ-first Abfrage – GROQ ermöglicht das Filtern, Projizieren, Joinen und Umformen von Daten in einer einzigen Abfrage. Im Gegensatz zu REST oder GraphQL geben GROQ-Abfragen genau die Form zurück, die Sie in der Projektion definieren.
- Content als Daten – Rich Text wird als Portable Text (eine JSON-basierte Spezifikation) gespeichert, was ihn in jedem Frontend-Framework ohne Vendor-Lock-In renderbar macht.
- API-Versionierung – Jeder Client muss einen
apiVersion-Datumsstring angeben. Dies pinnt Ihren Code auf ein bestimmtes API-Verhalten und verhindert, dass Breaking Changes die Produktion beeinträchtigen. - CDN-Caching – Öffentliche Leseabfragen verwenden
useCdn: truefür edge-gespeicherte Antworten. Mutationen und authentifizierte Lesezugriffe verwendenuseCdn: falsefür aktuelle Daten. - Typgenerierung – Sanity TypeGen generiert TypeScript-Typen sowohl aus Ihren Schemas als auch aus GROQ-Abfragen und ermöglicht so durchgängige Typsicherheit vom Content-Modell bis zum Frontend.
Wann Sanity verwenden:
- Content-getriebene Websites und Anwendungen (Blogs, Marketing-Seiten, Dokumentation)
- Projekte, die eine kollaborative Echtzeit-Bearbeitung in einem anpassbaren Studio benötigen
- Multi-Channel-Content-Auslieferung (Web, Mobil, IoT) aus einer einzigen Content-Quelle
- Teams, die typsichere Content-Abfragen mit GROQ und TypeGen wünschen
Wann NICHT verwenden:
- Transaktionsdaten, die ACID-Garantien erfordern (verwenden Sie eine Datenbank)
- Benutzergenerierte Inhalte in großem Maßstab (Sanity ist für redaktionelle Inhalte optimiert)
- Projekte, die ein selbst gehostetes CMS benötigen (Sanitys Content Lake ist gehostet, das Studio jedoch Open Source)
Kernmuster
Muster 1: Client-Einrichtung mit createClient
Konfigurieren Sie @sanity/client mit Projekt-ID, Dataset, API-Version und CDN-Präferenz. Setzen Sie immer apiVersion auf einen datierten String und useCdn explizit.
import { createClient } from "@sanity/client";
export const client = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: "2025-02-19", // Auf ein bestimmtes API-Versionsdatum pinnen
useCdn: true, // true für öffentliche Lesezugriffe, false für authentifizierte/aktuelle Daten
});
Für eine duale Client-Einrichtung (öffentlich + Vorschau mit Token) siehe examples/core.md.
Muster 2: GROQ-Abfragen mit Filtern und Projektionen
GROQ-Abfragen kombinieren Filter, Projektionen, Sortierung und Slicing. Verwenden Sie immer defineQuery() für TypeGen und $param für dynamische Werte.
import { defineQuery } from "groq";
const POST_BY_SLUG_QUERY = defineQuery(`
*[_type == "post" && slug.current == $slug][0]{
_id, title, body, "author": author->{name, image}
}
`);
const post = await client.fetch(POST_BY_SLUG_QUERY, { slug: "my-post" });
Interpolieren Sie niemals Benutzereingaben in GROQ-Strings – verwenden Sie immer $param-Parameter, um GROQ-Injection zu verhindern. Für fortgeschrittene Abfragen (kombinierte Abfragen, bedingte Projektionen) siehe examples/core.md.
Muster 3: Schema-Definitionen mit defineType und defineField
Definieren Sie die Inhaltsstruktur mit defineType, defineField und defineArrayMember aus "sanity" für Typsicherheit und Studio-UI.
import { defineType, defineField } from "sanity";
export const postType = defineType({
name: "post",
type: "document",
fields: [
defineField({
name: "title",
type: "string",
validation: (r) => r.required(),
}),
defineField({ name: "slug", type: "slug", options: { source: "title" } }),
],
});
Für vollständige Schemas (Bilder mit Hotspot, Arrays, Referenzen, Vorschauen, Objekttypen) siehe examples/schemas.md.
Muster 4: Portable Text Rendering
Rendern Sie Block-Content mit @portabletext/react. Definieren Sie benutzerdefinierte PortableTextComponents für nicht standardmäßige Blöcke (Bilder, Code) und Markierungen (Links, Hervorhebungen).
import { PortableText } from "@portabletext/react";
<PortableText value={body} components={components} />;
Verwenden Sie nicht das veraltete @sanity/block-content-to-react-Paket. Für vollständige Komponentenbeispiele siehe examples/rich-content.md.
Muster 5: Image-URL-Builder
Verwenden Sie @sanity/image-url, um optimierte, responsive Bild-URLs mit Zuschnitt- und Hotspot-Unterstützung zu generieren.
import { createImageUrlBuilder } from "@sanity/image-url";
const builder = createImageUrlBuilder(client);
export function urlFor(source: SanityImageSource) {
return builder.image(source);
}
// Verwendung: urlFor(image).width(800).auto("format").url()
Für responsive srcSet-Muster und Bildtransformationen siehe examples/rich-content.md.
Muster 6: Mutationen (Create, Patch, Delete)
Verwenden Sie die Methoden von @sanity/client für Dokumentenmutationen. Rufen Sie bei Patches und Transaktionen immer .commit() auf.
await client.create({ _type: "post", title: "Neuer Beitrag" });
await client.patch("post-123").set({ title: "Aktualisiert" }).commit();
await client.delete("post-123");
Für createOrReplace, createIfNotExists, Transaktionen, Array-Einfügungen und Sichtbarkeitsoptionen siehe examples/mutations.md.
Muster 7: Echtzeit-Listener
Abonnieren Sie Dokumentänderungen mit client.listen(). Der Listener verwendet nur den Filterteil von GROQ – Projektionen und Sortierung werden ignoriert.
const subscription = client.listen(`*[_type == "post"]`).subscribe({
next: (update) => {
/* update.transition: 'update' | 'appear' | 'disappear' */
},
error: (err) => console.error(err),
});
subscription.unsubscribe(); // Bereinigung nach Abschluss
Bewerten Sie für Produktions-Frontends die neuere Live Content API als einfachere Alternative. Für Listener-Optionen und Einschränkungen siehe examples/mutations.md.
Muster 8: TypeGen für typsichere GROQ
Konfigurieren Sie TypeGen in sanity.cli.ts mit overloadClientMethods: true für getypte client.fetch-Ergebnisse. Verwenden Sie defineQuery() aus "groq", um Abfragen für TypeGen auffindbar zu machen.
// sanity.cli.ts — setzen Sie typegen.overloadClientMethods: true
// queries/post-queries.ts — umschließen Sie Abfragen mit defineQuery()
// sanity.types.ts — automatisch generierte Ergebnistypen
const posts = await client.fetch(allPostsQuery); // Getyptes Ergebnis
Inline-Abfrage-Strings ohne defineQuery() erzeugen ungetypte (any) Ergebnisse. Für die vollständige TypeGen-Konfiguration und den Workflow siehe examples/core.md.
<decision_framework>
Entscheidungsrahmen
useCdn: true vs false
Sind die Daten öffentlich und nicht personalisiert?
├─ JA → useCdn: true (Edge-gecacht, schnell)
└─ NEIN →
├─ Wird ein Token für authentifizierte Lesezugriffe verwendet? → useCdn: false
├─ Werden Echtzeit-aktuelle Daten benötigt (Vorschau)? → useCdn: false
└─ Werden Mutationen durchgeführt? → useCdn: false
Entwurfshandhabung
Werden Entwurfsdokumente benötigt?
├─ JA → perspective: 'previewDrafts' verwenden (erfordert Token)
├─ NEIN → perspective: 'published' verwenden (Standard seit API v2025-02-19)
└─ Gemischt (Vorschaumodus umschaltbar)?
└─ Zwei Clients erstellen: einen öffentlichen (useCdn: true), einen für die Vorschau (Token + useCdn: false)
GROQ-Abfrage-Rückgabeform
Wie viele Dokumente werden erwartet?
├─ Eins (nach ID, Slug, Singleton) → [0] am Ende hinzufügen (gibt Objekt oder null zurück)
├─ Viele (Liste, Feed) → Kein Slice-Suffix (gibt Array zurück)
└─ Paginiert → [start...end] Slice hinzufügen
Bildverarbeitung
Wie sollen Bilder ausgeliefert werden?
├─ Feste Größe (Thumbnails, Avatare) → urlFor(img).width(W).height(H).url()
├─ Responsive (Artikelbilder) → srcSet mit mehreren Breiten
├─ Formatoptimierung → .auto('format') für WebP/AVIF
└─ Auf Seitenverhältnis zugeschnitten → .width(W).height(H).fit('crop')
Auswahl der Mutationsmethode
Welche Operation wird benötigt?
├─ Neues Dokument erstellen → client.create()
├─ Erstellen oder vollständig ersetzen → client.createOrReplace() (für Singletons)
├─ Nur erstellen, wenn nicht vorhanden → client.createIfNotExists()
├─ Bestimmte Felder aktualisieren → client.patch(id).set({...}).commit()
├─ Felder entfernen → client.patch(id).unset([...]).commit()
├─ Mehrere zusammenhängende Änderungen → client.transaction()...commit()
└─ Dokument löschen → client.delete(id)
</decision_framework>
<red_flags>
ROTE FLAGGEN
Hochprioritäre Probleme:
- Fehlende
apiVersionim Client – Das Weglassen vonapiVersionverwendet Legacy-API-Verhalten, das sich ohne Vorankündigung ändern kann. Immer auf einen Datumsstring pinnen. - String-Interpolation in GROQ-Abfragen – Die Interpolation von Benutzereingaben in GROQ-Strings ermöglicht GROQ-Injection. Immer
$param-Parameter verwenden. - Verwendung von
useCdn: truemit einem Token – CDN-gecachte Antworten ignorieren Authentifizierungstokens. Authentifizierte Abfragen müssenuseCdn: falseverwenden. - Zugriff auf Entwurfsdokumente ohne Token – Entwürfe (
drafts.*-Dokumente) erfordern einen API-Token undperspective: 'previewDrafts'. Ohne Token sind Entwürfe unsichtbar.
Mittelprioritäre Probleme:
- Abrufen aller Felder mit
{...}, wenn nur wenige benötigt werden – Over-Fetching verschwendet Bandbreite und CDN-Cache-Effizienz. Projizieren Sie nur die benötigten Felder. - Fehlender
.commit()-Aufruf bei Patches –client.patch(id).set({...})ohne.commit()tut nichts – die Mutation wird nie gesendet. - Nichtverwendung von
defineQuery()für GROQ-Abfragen – TypeGen kann keine Typen für Abfragen generieren, die nicht indefineQuery()eingeschlossen oder benannten Variablen zugewiesen sind. - Hartcodierte Projekt-ID oder Dataset – Verwenden Sie Umgebungsvariablen; hartcodierte Werte verhindern Umgebungswechsel und geben Projektdetails preis.
- Verwendung des veralteten
@sanity/block-content-to-react– Ersetzt durch@portabletext/react. Das alte Paket wird nicht mehr gewartet.
Häufige Fehler:
- Vergessenes
_keybei Array-Elementen in Mutationen – Jedes Element in einem Sanity-Array muss ein eindeutiges_key-Feld haben. Mutationen ohne_keyschlagen fehl. - Verwendung von
_idmitclient.create()–create()generiert eine zufällige_id. Wenn Sie_idangeben und das Dokument existiert, tritt ein Fehler auf. Verwenden SiecreateOrReplace()odercreateIfNotExists()für idempotente Operationen. - Nichtbehandlung des Falls, dass
[0]nullzurückgibt – Eine GROQ-Abfrage endet mit[0]kannnullzurückgeben, wenn kein Dokument der Abfrage entspricht. Überprüfen Sie immer aufnull, bevor Sie auf Felder zugreifen.
</red_flags>
<quick_reference>
Kurzreferenz
| Konzept | Befehl/Muster | Anmerkung |
|---|---|---|
| Client erstellen | createClient({projectId, dataset, apiVersion, useCdn}) | apiVersion ist erforderlich |
| GROQ-Abfrage | defineQuery(...) + client.fetch(query, params) | Verwenden Sie $param für dynamische Werte |
| Schema definieren | defineType({name, type, fields}) | Für Typsicherheit und Studio-UI |
| Portable Text rendern | <PortableText value={...} components={...} /> | Aus @portabletext/react |
| Bild-URL | urlFor(img).width(800).url() | Aus @sanity/image-url |
| Dokument erstellen | client.create({_type, ...}) | Generiert zufällige _id |
| Dokument patchen | client.patch(id).set({...}).commit() | .commit() ist erforderlich |
| Dokument löschen | client.delete(id) | - |
| Echtzeit-Listener | client.listen(groqFilter).subscribe({...}) | Nur Filter; Projektionen ignoriert |
| TypeGen generieren | sanity typegen generate | Erfordert defineQuery() |
</quick_reference>
Zusätzliche Informationen
Weitere Informationen zu den Prinzipien und der Entwicklung von Sanity finden Sie in den offiziellen Sanity-Dokumentationen unter sanity.io/docs. Spezifische API-Referenzen sind unter sanity.io/docs/reference verfügbar.