Communitygithub.com

Sanity Patterns

Sanity 構造化コンテンツプラットフォーム向けスキル — GROQ クエリ、スキーマ定義、@sanity/client、Portable Text、画像処理、リアルタイムリスナー、ミューテーション、TypeGen をカバーします。

Sanity Patterns とは?

このスキルは、Sanity 構造化コンテンツプラットフォームを効果的に使用するための包括的なパターン集です。開発者は `createClient` によるクライアント設定、`defineType`/`defineField` を用いた型安全なスキーマ定義、GROQ クエリ言語によるデータ取得、`@portabletext/react` を用いたリッチテキストレンダリング、`@sanity/image-url` による画像最適化、ミューテーションによるドキュメント操作、リアルタイムリスナー、そして Sanity TypeGen によるエンドツーエンドの型安全性確保といった主要なパターンを習得できます。このスキルは、`apiVersion` の明示的な設定、`useCdn` の適切な使い分け、GROQ インジェクションを防ぐためのパラメーター化クエリ、下書きドキュメントの明示的な処理など、Sanity 開発における重要なベストプラクティスを強調しています。Claude Code、Cursor、Codex などの AI エージェント向けに設計されており、`SKILL.md` の全内容に加え、詳細な使用例やリファレンスが別ファイルとして提供されています。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Sanity Patterns は何をしますか?

クイックガイド: Sanity を構造化コンテンツ管理に使用する場合、GROQ クエリ、defineType/defineField による型付きスキーマ、@sanity/client によるデータ取得を利用します。常に apiVersion を日付文字列に設定し、パブリック読み取りには useCdn: true を使用し、下書きドキュメントを明示的に処理し、画像変換には @sanity/image-url を、リッチテキストレンダリングには @portabletext/react を使用します。TypeScript の型は sanity typegen generate で生成します。


<critical_requirements>

クリティカル: このスキルを使用する前に

すべてのコードは CLAUDE.md のプロジェクト規約に従う必要があります(ケバブケース、名前付きエクスポート、インポート順序、import type、名前付き定数)

(createClient には必ず apiVersion を '2025-02-19' のような日付文字列で設定してください。省略するとレガシー API が使用され、壊れる可能性があります)

(パブリック読み取りクエリには useCdn: true を、トークンを使用する場合や新鮮なデータが必要な場合は useCdn: false を必ず使用してください)

(動的な値には必ずパラメーター化された GROQ クエリ ($param) を使用してください。ユーザー入力を GROQ 文字列に直接挿入しないでください)

(下書きは明示的に処理する必要があります。下書きドキュメントの _id には drafts. プレフィックスが付き、perspective: 'published' ではデフォルトで返されません)

(TypeGen の型生成のため、groq から defineQuery() を使用し、クエリを名前付き変数に割り当ててください)

</critical_requirements>


自動検出: 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

使用するタイミング:

  • @sanity/client を createClient でセットアップしてデータを取得する場合
  • GROQ クエリ(フィルター、プロジェクション、結合、並べ替え、スライス)を作成する場合
  • defineType、defineField、defineArrayMember でコンテンツスキーマを定義する場合
  • @portabletext/react で Portable Text(ブロックコンテンツ)をレンダリングする場合
  • @sanity/image-url で画像 URL を生成する場合(レスポンシブ画像、クロップ、ホットスポット)
  • ミューテーション(作成、部分更新、削除、トランザクション)を実行する場合
  • client.listen() でリアルタイムリスナーをセットアップする場合
  • Sanity TypeGen で TypeScript の型を生成する場合

カバーする主要パターン:

  • createClient と apiVersion 設定によるクライアントセットアップ
  • GROQ クエリ言語: フィルター、プロジェクション、並べ替え、スライス、結合、参照
  • スキーマ定義: ドキュメントタイプ、オブジェクトタイプ、配列、参照、画像
  • カスタムコンポーネントによる Portable Text レンダリング
  • レスポンシブ画像と変換を備えた画像 URL ビルダー
  • ミューテーション: 作成、作成または置換、部分更新、削除、トランザクション
  • client.listen() によるリアルタイムリスナー
  • defineQuery() を使用した型安全な GROQ クエリのための TypeGen

使用すべきでないタイミング:

  • GraphQL のみの API(Sanity は主に GROQ をサポートします。必要に応じて GraphQL スキルを使用してください)
  • データベースへの直接アクセス(Sanity はホスト型コンテンツレイクであり、データベースではありません)
  • Sanity 以外の CMS プラットフォーム(使用する CMS に専用のスキルを使用してください)

詳細リソース:

  • 意思決定フレームワークとクイックリファレンステーブルについては、reference.md を参照してください

クライアント & GROQ:

  • examples/core.md — クライアントセットアップ、GROQ クエリ、エラーハンドリング、TypeGen

スキーマ:

  • examples/schemas.md — defineType、defineField、ドキュメントタイプ、オブジェクトタイプ、参照、画像

リッチコンテンツ:

ミューテーション & リアルタイム:

  • examples/mutations.md — 作成、部分更新、削除、トランザクション、リアルタイムリスナー

哲学

Sanity は、リアルタイムコンテンツレイク、クエリ言語としての GROQ(Graph-Relational Object Queries)、そしてカスタマイズ可能な編集環境としての Sanity Studio を中心に構築された構造化コンテンツプラットフォームです。

コア原則:

  1. 構造化コンテンツ — コンテンツは、形状、検証、編集 UI を記述するスキーマ(defineType、defineField)によって定義されます。スキーマはコードであり、設定ファイルではありません。
  2. GROQ ファーストのクエリ — GROQ を使用すると、1 つのクエリでデータのフィルタリング、プロジェクション、結合、再形成ができます。REST や GraphQL とは異なり、GROQ クエリはプロジェクションで定義した通りの形状を正確に返します。
  3. データとしてのコンテンツ — リッチテキストは Portable Text(JSON ベースの仕様)として保存されるため、ベンダーロックインなく任意のフロントエンドフレームワークでレンダリングできます。
  4. API バージョニング — すべてのクライアントは apiVersion 日付文字列を指定する必要があります。これにより、コードは特定の API 動作に固定され、破壊的な変更が本番環境に影響を与えるのを防ぎます。
  5. CDN キャッシング — パブリック読み取りクエリは、エッジキャッシュされた応答のために useCdn: true を使用します。ミューテーションと認証された読み取りは、新鮮なデータのために useCdn: false を使用します。
  6. 型生成 — Sanity TypeGen は、スキーマと GROQ クエリの両方から TypeScript の型を生成し、コンテンツモデルからフロントエンドまでエンドツーエンドの型安全性を実現します。

Sanity を使用するタイミング:

  • コンテンツ駆動型のウェブサイトやアプリケーション(ブログ、マーケティングサイト、ドキュメント)
  • カスタマイズ可能なスタジオでリアルタイムの共同編集が必要なプロジェクト
  • 1 つのコンテンツソースからのマルチチャネルコンテンツ配信(ウェブ、モバイル、IoT)
  • GROQ と TypeGen を使用した型安全なコンテンツクエリを求めるチーム

使用すべきでないタイミング:

  • ACID 保証が必要なトランザクションデータ(データベースを使用してください)
  • 大規模なユーザー生成コンテンツ(Sanity は編集コンテンツ向けに最適化されています)
  • セルフホスト型 CMS が必要なプロジェクト(Sanity のコンテンツレイクはホスト型ですが、Studio はオープンソースです)

コアパターン

パターン 1: createClient によるクライアントセットアップ

プロジェクト ID、データセット、API バージョン、CDN 設定を使用して @sanity/client を設定します。常に apiVersion を日付文字列に設定し、useCdn を明示的に指定します。

import { createClient } from "@sanity/client";

export const client = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: "2025-02-19", // 特定の API バージョン日付に固定
  useCdn: true, // パブリック読み取りは true、認証/新鮮なデータには false
});

デュアルクライアントセットアップ(パブリック + トークンを使用したプレビュー)については、examples/core.md を参照してください。


パターン 2: フィルターとプロジェクションを使用した GROQ クエリ

GROQ クエリは、フィルター、プロジェクション、並べ替え、スライスを組み合わせます。TypeGen には常に defineQuery() を、動的な値には $param を使用してください。

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" });

ユーザー入力を GROQ 文字列に直接挿入しないでください。GROQ インジェクションを防ぐために、常に $param パラメーターを使用してください。高度なクエリ(複合クエリ、条件付きプロジェクション)については、examples/core.md を参照してください。


パターン 3: defineType と defineField を使用したスキーマ定義

型安全性と Studio UI のために、"sanity" から defineType、defineField、defineArrayMember を使用してコンテンツ構造を定義します。

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" } }),
  ],
});

完全なスキーマ(ホットスポット付き画像、配列、参照、プレビュー、オブジェクトタイプ)については、examples/schemas.md を参照してください。


パターン 4: Portable Text レンダリング

@portabletext/react を使用してブロックコンテンツをレンダリングします。非標準のブロック(画像、コード)やマーク(リンク、ハイライト)については、カスタム PortableTextComponents を定義します。

import { PortableText } from "@portabletext/react";
<PortableText value={body} components={components} />;

非推奨の @sanity/block-content-to-react パッケージは使用しないでください。完全なコンポーネント例については、examples/rich-content.md を参照してください。


パターン 5: 画像 URL ビルダー

@sanity/image-url を使用して、クロップとホットスポットをサポートする最適化されたレスポンシブ画像 URL を生成します。

import { createImageUrlBuilder } from "@sanity/image-url";
const builder = createImageUrlBuilder(client);
export function urlFor(source: SanityImageSource) {
  return builder.image(source);
}
// 使用法: urlFor(image).width(800).auto("format").url()

レスポンシブな srcSet パターンと画像変換については、examples/rich-content.md を参照してください。


パターン 6: ミューテーション(作成、部分更新、削除)

ドキュメントミューテーションには @sanity/client のメソッドを使用します。パッチとトランザクションでは常に .commit() を呼び出してください。

await client.create({ _type: "post", title: "New Post" });
await client.patch("post-123").set({ title: "Updated" }).commit();
await client.delete("post-123");

createOrReplace、createIfNotExists、トランザクション、配列への挿入、可視性オプションについては、examples/mutations.md を参照してください。


パターン 7: リアルタイムリスナー

client.listen() でドキュメントの変更を購読します。リスナーは GROQ のフィルター部分のみを使用します。プロジェクションと並べ替えは無視されます。

const subscription = client.listen(`*[_type == "post"]`).subscribe({
  next: (update) => {
    /* update.transition: 'update' | 'appear' | 'disappear' */
  },
  error: (err) => console.error(err),
});
subscription.unsubscribe(); // 完了したらクリーンアップ

本番用フロントエンドでは、よりシンプルな代替手段として新しい Live Content API を評価してください。リスナーオプションと注意事項については、examples/mutations.md を参照してください。


パターン 8: 型安全な GROQ のための TypeGen

sanity.cli.ts で TypeGen を overloadClientMethods: true で設定し、型付きの client.fetch 結果を取得します。"groq" から defineQuery() を使用して、TypeGen がクエリを検出できるようにします。

// sanity.cli.ts — typegen.overloadClientMethods: true を設定
// queries/post-queries.ts — クエリを defineQuery() でラップ
// sanity.types.ts — 自動生成された結果の型
const posts = await client.fetch(allPostsQuery); // 型付き結果

defineQuery() を使用しないインラインクエリ文字列は、型付けされていない(any)結果を生成します。完全な TypeGen 設定とワークフローについては、examples/core.md を参照してください。


<decision_framework>

意思決定フレームワーク

useCdn: true vs false

データはパブリックでパーソナライズされていないか?
├─ YES → useCdn: true(エッジキャッシュ、高速)
└─ NO →
    ├─ 認証読み取りにトークンを使用? → useCdn: false
    ├─ リアルタイムの新鮮なデータ(プレビュー)が必要? → useCdn: false
    └─ ミューテーションを実行? → useCdn: false

下書き処理

下書きドキュメントが必要か?
├─ YES → perspective: 'previewDrafts' を使用(トークンが必要)
├─ NO → perspective: 'published' を使用(API v2025-02-19 以降のデフォルト)
└─ 混合(プレビューモードの切り替え)?
    └─ 2 つのクライアントを作成: 1 つはパブリック(useCdn: true)、もう 1 つはプレビュー用(トークン + useCdn: false)

GROQ クエリの戻り値の形状

期待するドキュメント数は?
├─ 1 件(ID、スラッグ、シングルトンによる) → 末尾に [0] を追加(オブジェクトまたは null を返す)
├─ 複数件(リスト、フィード) → スライス接尾辞なし(配列を返す)
└─ ページネーション → [start...end] スライスを追加

画像処理

画像はどのように配信されるべきか?
├─ 固定サイズ(サムネイル、アバター) → urlFor(img).width(W).height(H).url()
├─ レスポンシブ(記事画像) → 複数の幅で srcSet
├─ フォーマット最適化 → .auto('format') で WebP/AVIF
└─ アスペクト比にクロップ → .width(W).height(H).fit('crop')

ミューテーションメソッドの選択

必要な操作は?
├─ 新しいドキュメントを作成 → client.create()
├─ 作成または完全に置換 → client.createOrReplace()(シングルトン用)
├─ 存在しない場合のみ作成 → client.createIfNotExists()
├─ 特定のフィールドを更新 → client.patch(id).set({...}).commit()
├─ フィールドを削除 → client.patch(id).unset([...]).commit()
├─ 複数の関連変更 → client.transaction()...commit()
└─ ドキュメントを削除 → client.delete(id)

</decision_framework>


<red_flags>

RED FLAGS(警告フラグ)

高優先度の問題:

  • クライアントの apiVersion 欠落 — apiVersion を省略すると、予告なく変更される可能性のあるレガシー API 動作が使用されます。常に日付文字列で固定してください。
  • GROQ クエリへの文字列補間 — ユーザー入力を GROQ 文字列に補間すると、GROQ インジェクションが可能になります。常に $param パラメーターを使用してください。
  • トークンでの useCdn: true の使用 — CDN キャッシュされた応答は認証トークンを無視します。認証されたクエリは useCdn: false を使用する必要があります。
  • トークンなしでの下書きドキュメントへのアクセス — 下書き(drafts.* ドキュメント)には API トークンと perspective: 'previewDrafts' が必要です。トークンがないと下書きは見えません。

中優先度の問題:

  • 必要なフィールドが少ないのに {...} ですべてのフィールドを取得 — 過剰なフェッチは帯域幅と CDN キャッシュ効率を浪費します。必要なフィールドのみをプロジェクションしてください。
  • パッチでの .commit() の欠落 — client.patch(id).set({...}) は .commit() なしでは何も行いません。ミューテーションは送信されません。
  • GROQ クエリに defineQuery() を使用していない — TypeGen は defineQuery() でラップされるか名前付き変数に割り当てられていないクエリの型を生成できません。
  • ハードコードされたプロジェクト ID またはデータセット — 環境変数を使用してください。ハードコードされた値は環境の切り替えを妨げ、プロジェクトの詳細を漏洩します。
  • 非推奨の @sanity/block-content-to-react の使用 — @portabletext/react に置き換えられました。古いパッケージはメンテナンスされていません。

よくある間違い:

  • ミューテーションでの配列アイテムの _key 忘れ — Sanity 配列のすべてのアイテムには一意の _key フィールドが必要です。_key がないミューテーションは失敗します。
  • client.create() での _id の使用 — create() はランダムな _id を生成します。_id を指定してドキュメントが存在するとエラーになります。冪等な操作には createOrReplace() または createIfNotExists() を使用してください。
  • [0] が null を返すケースの処理漏れ — 末尾に [0] を付けても結果が null になる可能性があります。後続のプロパティアクセスを行う前に必ず null チェックを行うか、?? を使用してください。
  • fetch で @sanity/preview-kit を使用する際の perspective の欠落 — プレビューモードでは perspective: 'previewDrafts' が設定されたプレビュークライアントを使用する必要があります。そうしないと、下書きはクエリ結果に表示されません。

</red_flags>

Individual skills in this repo

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

関連スキル