本文へ移動
cccskills
無料GitHub で公開

sanity-cms

Master reference for Sanity CMS integration in this portfolio. Load this skill whenever working with Sanity schemas, queries, data, webhooks, media, draft mode, or any CMS operation. Covers all 20 document types, GROQ patterns, client configuration, caching, revalidation, and studio structure.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md16.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Sanity CMS — Master Reference

This is the canonical reference for the Sanity CMS integration. Load this skill for ANY Sanity-related task: adding/editing content, modifying schemas, writing queries, debugging data issues, or understanding the data flow.

Quick Facts

PropertyValue
Project IDnl0qw78w
Datasetproduction
API Version2026-02-19
Studio URLhttps://namias-cms.sanity.studio/
Site URLhttps://namias.tech
Schema Locationstudio/schemaTypes/
Types Locationsrc/types/index.ts
Queries Locationsrc/lib/cms-content.server.ts
Client Locationsrc/sanity/lib/client.ts
Webhook EndpointPOST /api/sanity/webhook

Environment Variables

VariablePurposeRequired
NEXT_PUBLIC_SANITY_PROJECT_IDPublic project IDYes
NEXT_PUBLIC_SANITY_DATASETPublic datasetYes
SANITY_API_READ_TOKENAuthenticated readsYes
SANITY_API_WRITE_TOKENMutations/scriptsFor scripts
SANITY_REVALIDATE_SECRETWebhook authYes
SANITY_MEDIA_GATEWAY_SECRETHMAC for media URLsYes
SANITY_STUDIO_URLStudio URL for env.tsOptional
NEXT_PUBLIC_SANITY_STUDIO_URLPublic studio URLOptional

Document Types (20 total)

Singletons (7)

TypeFileDescription
profileprofile.tsHero info, bio, education, social links
aboutSectionaboutSection.tsAbout content (portable text)
siteSettingssiteSettings.tsAll site-wide settings (600+ lines)
seoSettingsseoSettings.tsDefault SEO meta
mediaSettingsmediaSettings.tsImage defaults, quality presets
techStacktechStack.tsTechnology list with logos/categories
resumeresume.tsResume PDF file reference

Collections (8)

TypeFileKey Fields
experienceexperience.tsrole, company, startDate, endDate, highlights, images
projectproject.tstitle, slug, summary, technologies, gallery, showcaseDetail
certificationcertification.tstitle, issuer (ref), issuedAt, expiresAt, credentialUrl
galleryImagegalleryImage.tstitle, image, category (ref), mediaType, tags
recommendationrecommendation.tsquote, name, title, company, avatar
membershipmembership.tsname, url, joinedAt
postpost.tstitle, slug, body (portable text), published, publishAt
authorauthor.tsname, slug, image, bio (portable text)

Reference Types (4)

TypeFileReferenced By
categorycategory.tspost.categories[]
certificationCategorycertificationCategory.tscertification.category
certificationIssuercertificationIssuer.tscertification.issuer
galleryCategorygalleryCategory.tsgalleryImage.category

Array Types (1)

TypeFileUsed By
blockContentblockContent.tspost.body, author.bio, aboutSection.aboutContent

Schema Patterns

Slug Pattern

defineField({
  name: 'slug',
  type: 'slug',
  options: { source: 'title', maxLength: 96 },
  validation: uniqueSlug, // Custom async validator
})

Format: /^[a-z0-9]+(?:-[a-z0-9]+)*$/

Image Pattern (Standard)

defineField({
  name: 'image',
  type: 'image',
  options: { hotspot: true },
  fields: [
    { name: 'alt', type: 'string', validation: requireAltText },
    { name: 'caption', type: 'string' },
    { name: 'credit', type: 'string' },
    { name: 'source', type: 'string' },
    { name: 'license', type: 'string' },
  ],
})

Reference Pattern

defineField({
  name: 'issuer',
  type: 'reference',
  to: [{ type: 'certificationIssuer' }],
  options: { disableNew: false }, // Allows inline creation
})

Order/Sort Pattern

Collections use numeric order field for manual sorting:

defineField({ name: 'order', type: 'number' })

Status Enum Pattern

defineField({
  name: 'status',
  type: 'string',
  options: {
    list: [
      { title: 'Completed', value: 'completed' },
      { title: 'In progress', value: 'in-progress' },
      // ...
    ],
  },
})

Scheduled Publishing

Both project and post have publishAt: datetime for future publishing. A Sanity Function (scheduled-publish) runs every 5 minutes to promote past-due documents.

Computed Fields

  • experience.computedDuration — Custom ExperienceDurationField component
  • post.computedReadingTime — Custom ReadingTimeField component

GROQ Queries

Master Data Fetch (src/lib/cms-content.server.ts)

All data flows through getCmsContent() which calls querySanity<T>() for each document type. Each query uses cache tags for targeted invalidation.

Profile:

*[_type == "profile"][0]{
  fullName, title, email, phone, location, github, linkedin, summary,
  "avatarUrl": avatar.asset->url,
  "profileImageUrl": profileImage.asset->url,
  resumeUrl, availabilityLabel, heroRoles,
  socialLinks[]{platform, icon, url, placements},
  highlights, education
}

Experience:

*[_type == "experience"] | order(order asc, startDate desc){
  role, company, location, startDate, endDate, employmentType, workModel,
  summary, featuredStory, highlights, tags, achievements, images
}

Projects (list):

*[_type == "project"] | order(order asc, featuredRank asc, title asc){
  title, "slug": slug.current, summary, challenge, solution, result,
  year, category, featured, role, technologies, achievements,
  featuredRank, status, liveUrl, repositoryUrl,
  "imageUrl": image.asset->url, "imageAlt": image.alt,
  tier, showcaseDetail, shortDescription, highlights, githubRepo,
  "galleryItems": gallery[]{
    "url": asset->url, alt, caption, credit, source, license
  }
}

Project (single by slug):

*[_type == "project" && slug.current == "${safeSlug}"][0]{...}

Certifications:

*[_type == "certification"] | order(order asc, issuedAt desc){
  title, issuedAt, tags,
  "issuer": issuer->title,
  "imageUrl": image.asset->url, "alt": image.alt
}

Blog Posts:

*[_type == "post" && published == true && defined(slug.current)]
  | order(publishedAt desc){
  title, "slug": slug.current, excerpt, readTime, body, tags,
  publishedAt, featured, metaTitle, metaDescription,
  "mainImageUrl": mainImage.asset->url,
  "author": author->name,
  "categories": categories[]->title
}

Gallery:

*[_type == "galleryImage"] | order(order asc, capturedAt desc){
  title, mediaType, tags, capturedAt,
  "category": category->title,
  "mediaUrl": image.asset->url, alt
}

Site Settings:

*[_type == "siteSettings"][0]{
  footer{...}, blog{...},
  siteName, siteTagline, ownerName, contactEmail,
  "ogImageUrl": ogImage.asset->url,
  "twitterImageUrl": twitterImage.asset->url
}

Static Params:

// Blog slugs
*[_type == "post" && published == true && defined(slug.current)]{"slug": slug.current}
// Project slugs (showcase)
*[_type == "project" && showcaseDetail == true && defined(slug.current)]{"slug": slug.current}

Data Flow Architecture

Sanity CMS (cdn.sanity.io)
    │
    ├─► Public Client (CDN, published perspective)
    │     └─► querySanity() with L1 (in-memory Map) + L2 (Redis) cache
    │           └─► getCmsContent() → Promise.all() for all doc types
    │                 └─► Server Components render with data
    │
    ├─► Preview Client (no CDN, previewDrafts perspective)
    │     └─► Used when draftMode() is enabled
    │     └─► Stega enabled for click-to-edit
    │
    ├─► Read Client (authenticated, no CDN)
    │     └─► Used for draft-mode enable endpoint
    │
    └─► Media Gateway (/api/media/sanity/{encoded})
          └─► HMAC-signed URLs, proxied from Sanity CDN
          └─► Smart caching headers based on URL expiry

Cache Strategy

LayerTTLStale-while-revalidateScope
L1 (in-memory Map)5 min60sPer-request dedup
L2 (Redis/Upstash)5 min60sCross-request
CDN (Sanity)Varies—Edge

Cache Invalidation (Webhook)

When content changes in Sanity studio:

  1. Studio triggers POST /api/sanity/webhook with secret header
  2. Webhook validates secret via timing-safe comparison
  3. Maps document type to cache tags (e.g., project → ['cms:project', 'cms:project-list'])
  4. Calls invalidateByTag() to clear L1 + L2
  5. Calls revalidatePath() for affected routes: /, /blog, /blog/[slug], /projects/[slug], /sitemap.xml

Media Gateway

All Sanity images are served through /api/media/sanity/{base64url-encoded-target}:

  • URL format: /api/media/sanity/{encoded}?w=1200&q=85&exp={expiresAt}&sig={hmac}
  • HMAC signing: Uses SANITY_MEDIA_GATEWAY_SECRET
  • Expiry: 7 days default
  • Proxy: Validates target is Sanity CDN, verifies HMAC, proxies response
  • Cache headers: Signed URLs use remaining expiry, files get immutable, images get 86400s

Building Media URLs

import { buildMediaGatewayUrl } from '@/lib/media-gateway'

// For server components (with HMAC):
const url = buildMediaGatewayUrl(sanityCdnUrl, { width: 1200, quality: 85 })

// For client components (no HMAC):
import { resolveContentImageSrc } from '@/lib/media'
const url = resolveContentImageSrc(sanityCdnUrl, { width: 1200 })

Draft Mode / Visual Editing

  1. Enable: Studio calls /api/draft-mode/enable?secret=...
  2. Client selection: pickClient() checks draftMode():
    • Draft ON → Preview client (previewDrafts, stega enabled)
    • Draft OFF → Public client (published, CDN)
  3. Stega: Embeds data-sanity attributes for click-to-edit
  4. Data attributes: Use sanityField() from @/utils/sanity-data-attribute

Studio Structure

Content
  Homepage
    Hero & Profile (singleton)
    About Section (singleton)
    Tech Stack (singleton)
  Collections
    Projects (filtered: All / Featured / Showcase)
    Experience (filtered: All / Current / Past)
    Certifications
    Gallery
    Resume
  Blog
    Posts (filtered: All / Published / Drafts)
    Authors
    Categories
  Community
    Memberships
    Recommendations
Settings
  Site Settings (singleton)
  SEO Settings (singleton)
  Media Settings (singleton)
Reference Data
  Certification Categories
  Certification Issuers
  Gallery Categories

Validation Rules (studio/validation/rules.ts)

RulePurpose
uniqueSlugRegex + async uniqueness check
uniqueTitleAsync title uniqueness check
requireAltTextAlt text 4+ characters
summaryLength40-280 chars
seoTitleLength30-60 chars
seoDescriptionLength120-160 chars
httpsOnlyURL must be https
dateOrderIssue date before expiry
endDateAfterStartCross-field date validation
requiredForPublishConditional required fields
maxArrayItems({max})Array length cap
seoImageDimensionsMin 1200x630 pixels

Adding a New Document Type

  1. Create schema file: studio/schemaTypes/myType.ts
  2. Add to registry: studio/schemaTypes/_registry.ts (DOCUMENT_META)
  3. Export in index: studio/schemaTypes/index.ts
  4. Add to desk structure: studio/structure/deskStructure.ts
  5. Add preview location: studio/preview/previewLocations.ts
  6. Add template: studio/templates/index.ts
  7. Add GROQ query: src/lib/cms-content.server.ts
  8. Add cache tag mapping: webhook SANITY_TYPE_TO_TAGS
  9. Add TypeScript type: src/types/index.ts
  10. Add to CmsContent interface: src/lib/cms-content.shared.ts
  11. Add fallback data: src/lib/cms-content.shared.ts
  12. Add to backup script: studio/scripts/backup-sanity.mjs

Common Tasks

Add a field to an existing schema

  1. Edit schema file in studio/schemaTypes/
  2. Update TypeScript type in src/types/index.ts
  3. Update GROQ query in src/lib/cms-content.server.ts
  4. Update CmsContent interface if needed
  5. Update fallback data if needed
  6. Test with npm run dev (studio)

Add a new singleton

  1. Create schema with type: 'singleton'
  2. Add to registry, index, desk structure
  3. Add query to cms-content.server.ts
  4. Add type to src/types/index.ts
  5. Add to webhook cache tags

Debug a data issue

  1. Check Sanity studio: is the document published?
  2. Check cache: call clearCmsQueryCache() or wait 5 min
  3. Check webhook: is revalidation firing? (check /api/sanity/webhook logs)
  4. Check query: test GROQ in Vision tool in studio
  5. Check types: do TypeScript types match the schema?

Add a new collection

  1. Create schema with type: 'document'
  2. Add order field for manual sorting
  3. Add orderings array
  4. Add status/featured fields if needed
  5. Follow the "Add a new document type" checklist above

Backup & Restore

CommandPurpose
npm run sanity:seedSeed demo data
studio/scripts/backup.ps1Export NDJSON backup (Windows)
studio/scripts/restore.ps1Import NDJSON backup (Windows)
studio/scripts/verify.ps1Verify backup integrity

Sanity Functions (Blueprints)

FunctionTriggerPurpose
scheduled-publishCron */5 * * * *Promotes documents where publishAt has passed
broken-refsCron 0 */6 * * *Scans for broken document references
auto-tag-imagesImage asset createAuto-tags uploaded images

Troubleshooting

SymptomLikely CauseFix
Data not updatingCache not invalidatedCheck webhook secret, trigger revalidation
Stale contentCDN cachingAdd ?t={timestamp} or clear cache
Draft not showingDraft mode not enabledCheck /api/draft-mode/enable endpoint
Image 404HMAC mismatchCheck SANITY_MEDIA_GATEWAY_SECRET
Query returns emptyWrong dataset or type nameVerify in Vision tool
Build failsMissing env varsCheck .env.local has all required vars

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Audit and fix accessibility issues across the portfolio to ensure WCAG 2.1 AA compliance.

日本語の概要は準備中です。原文の説明を表示しています。

PP-Namias/Portfolio22026年10月13日 更新

Creates a new blog post document in the Sanity studio. Use when the user wants to write a blog post, publish an article, or schedule a future post.

日本語の概要は準備中です。原文の説明を表示しています。

PP-Namias/Portfolio22026年10月13日 更新

Creates a new certification document in the Sanity studio. Use when the user wants to add a credential, certificate, or professional accreditation to the Certifications collection.

日本語の概要は準備中です。原文の説明を表示しています。

PP-Namias/Portfolio22026年10月13日 更新

Adds a new skill MD file to the studio's skills collection. Use when the user wants to author a new step-by-step recipe for the studio's in-app Skills tool or the .agents mirror.

日本語の概要は準備中です。原文の説明を表示しています。

PP-Namias/Portfolio22026年10月13日 更新

Creates a new project document in the Sanity studio. Use when the user wants to add a new portfolio project, a case study, or a work sample to the Projects collection.

日本語の概要は準備中です。原文の説明を表示しています。

PP-Namias/Portfolio22026年10月13日 更新

Creates a new experience document in the Sanity studio. Use when the user wants to add a job, role, internship, or work history entry to the Experience collection.

日本語の概要は準備中です。原文の説明を表示しています。

PP-Namias/Portfolio22026年10月13日 更新

PP-Namias のスキルをすべて見る

このスキルの問題を報告する