Communitygithub.com

Sanity Patterns

Strukturierte Content-Plattform – GROQ-Abfragen, Schema-Definitionen, @sanity/client, Portable Text, Bildbearbeitung, Echtzeit-Listener, Mutationen, TypeGen

Was ist Sanity Patterns?

Dieses Skill bietet umfassende Richtlinien und Muster für die Arbeit mit Sanity, einer strukturierten Content-Plattform, die auf einem Content Lake aufbaut. Es deckt die gesamte Pipeline ab: von der Client-Konfiguration mit `createClient` über die Definition von Inhaltsschemata mit `defineType` und `defineField` bis hin zur Erstellung von GROQ-Abfragen und der Typgenerierung mit Sanity TypeGen. Besonderer Wert wird auf Best Practices gelegt, wie die obligatorische Angabe von `apiVersion`, die korrekte Verwendung von `useCdn` für öffentliche versus authentifizierte Lesevorgänge und die strikte Vermeidung von Injection-Angriffen durch parametrisierte GROQ-Abfragen (`$param`). Entwickler lernen, wie sie Portable Text mit `@portabletext/react` rendern, optimierte Bild-URLs mit `@sanity/image-url` erzeugen, Mutationen (create, patch, delete) durchführen und Echtzeit-Listener für Content-Updates einrichten. Das Skill ist plattformunabhängig und kann mit jedem Frontend-Framework (React, Next.js, Vue, Svelte, etc.) verwendet werden, da Sanity headless arbeitet. Die detaillierten Code-Beispiele und Entscheidungsrahmen helfen, häufige Fehler zu vermeiden und eine robuste, typsichere Sanity-Integration aufzubauen.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-cms-sanity

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht Sanity Patterns?

Kurzanleitung: Verwenden Sie Sanity für strukturiertes Content-Management mit GROQ-Abfragen, getypten Schemas via defineType/defineField und @sanity/client für das Datenabrufen. Setzen Sie apiVersion immer auf einen datierten String, verwenden Sie useCdn: true für öffentliche Lesezugriffe, behandeln Sie Entwurfsdokumente explizit, nutzen Sie @sanity/image-url für Bildtransformationen und rendern Sie Rich Text mit @portabletext/react. Generieren Sie TypeScript-Typen mit sanity 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/client mit createClient zum 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 createClient und apiVersion-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:

Mutationen & Echtzeit:


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:

  1. Strukturierter Content – Content wird durch Schemas (defineType, defineField) definiert, die Form, Validierung und Bearbeitungs-UI beschreiben. Schemas sind Code, keine Konfigurationsdateien.
  2. 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.
  3. 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.
  4. 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.
  5. CDN-Caching – Öffentliche Leseabfragen verwenden useCdn: true für edge-gespeicherte Antworten. Mutationen und authentifizierte Lesezugriffe verwenden useCdn: false für aktuelle Daten.
  6. 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 apiVersion im Client – Das Weglassen von apiVersion verwendet 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: true mit einem Token – CDN-gecachte Antworten ignorieren Authentifizierungstokens. Authentifizierte Abfragen müssen useCdn: false verwenden.
  • Zugriff auf Entwurfsdokumente ohne Token – Entwürfe (drafts.*-Dokumente) erfordern einen API-Token und perspective: '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 in defineQuery() 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 _key bei Array-Elementen in Mutationen – Jedes Element in einem Sanity-Array muss ein eindeutiges _key-Feld haben. Mutationen ohne _key schlagen fehl.
  • Verwendung von _id mit client.create() – create() generiert eine zufällige _id. Wenn Sie _id angeben und das Dokument existiert, tritt ein Fehler auf. Verwenden Sie createOrReplace() oder createIfNotExists() für idempotente Operationen.
  • Nichtbehandlung des Falls, dass [0] null zurückgibt – Eine GROQ-Abfrage endet mit [0] kann null zurückgeben, wenn kein Dokument der Abfrage entspricht. Überprüfen Sie immer auf null, bevor Sie auf Felder zugreifen.

</red_flags>


<quick_reference>

Kurzreferenz

KonzeptBefehl/MusterAnmerkung
Client erstellencreateClient({projectId, dataset, apiVersion, useCdn})apiVersion ist erforderlich
GROQ-AbfragedefineQuery(...) + client.fetch(query, params)Verwenden Sie $param für dynamische Werte
Schema definierendefineType({name, type, fields})Für Typsicherheit und Studio-UI
Portable Text rendern<PortableText value={...} components={...} />Aus @portabletext/react
Bild-URLurlFor(img).width(800).url()Aus @sanity/image-url
Dokument erstellenclient.create({_type, ...})Generiert zufällige _id
Dokument patchenclient.patch(id).set({...}).commit().commit() ist erforderlich
Dokument löschenclient.delete(id)-
Echtzeit-Listenerclient.listen(groqFilter).subscribe({...})Nur Filter; Projektionen ignoriert
TypeGen generierensanity typegen generateErfordert 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.

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

Verwandte Skills