Audit and fix accessibility issues across the portfolio to ensure WCAG 2.1 AA compliance.
日本語の概要は準備中です。原文の説明を表示しています。
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.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
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.
| Property | Value |
|---|---|
| Project ID | nl0qw78w |
| Dataset | production |
| API Version | 2026-02-19 |
| Studio URL | https://namias-cms.sanity.studio/ |
| Site URL | https://namias.tech |
| Schema Location | studio/schemaTypes/ |
| Types Location | src/types/index.ts |
| Queries Location | src/lib/cms-content.server.ts |
| Client Location | src/sanity/lib/client.ts |
| Webhook Endpoint | POST /api/sanity/webhook |
| Variable | Purpose | Required |
|---|---|---|
NEXT_PUBLIC_SANITY_PROJECT_ID | Public project ID | Yes |
NEXT_PUBLIC_SANITY_DATASET | Public dataset | Yes |
SANITY_API_READ_TOKEN | Authenticated reads | Yes |
SANITY_API_WRITE_TOKEN | Mutations/scripts | For scripts |
SANITY_REVALIDATE_SECRET | Webhook auth | Yes |
SANITY_MEDIA_GATEWAY_SECRET | HMAC for media URLs | Yes |
SANITY_STUDIO_URL | Studio URL for env.ts | Optional |
NEXT_PUBLIC_SANITY_STUDIO_URL | Public studio URL | Optional |
| Type | File | Description |
|---|---|---|
profile | profile.ts | Hero info, bio, education, social links |
aboutSection | aboutSection.ts | About content (portable text) |
siteSettings | siteSettings.ts | All site-wide settings (600+ lines) |
seoSettings | seoSettings.ts | Default SEO meta |
mediaSettings | mediaSettings.ts | Image defaults, quality presets |
techStack | techStack.ts | Technology list with logos/categories |
resume | resume.ts | Resume PDF file reference |
| Type | File | Key Fields |
|---|---|---|
experience | experience.ts | role, company, startDate, endDate, highlights, images |
project | project.ts | title, slug, summary, technologies, gallery, showcaseDetail |
certification | certification.ts | title, issuer (ref), issuedAt, expiresAt, credentialUrl |
galleryImage | galleryImage.ts | title, image, category (ref), mediaType, tags |
recommendation | recommendation.ts | quote, name, title, company, avatar |
membership | membership.ts | name, url, joinedAt |
post | post.ts | title, slug, body (portable text), published, publishAt |
author | author.ts | name, slug, image, bio (portable text) |
| Type | File | Referenced By |
|---|---|---|
category | category.ts | post.categories[] |
certificationCategory | certificationCategory.ts | certification.category |
certificationIssuer | certificationIssuer.ts | certification.issuer |
galleryCategory | galleryCategory.ts | galleryImage.category |
| Type | File | Used By |
|---|---|---|
blockContent | blockContent.ts | post.body, author.bio, aboutSection.aboutContent |
defineField({
name: 'slug',
type: 'slug',
options: { source: 'title', maxLength: 96 },
validation: uniqueSlug, // Custom async validator
})
Format: /^[a-z0-9]+(?:-[a-z0-9]+)*$/
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' },
],
})
defineField({
name: 'issuer',
type: 'reference',
to: [{ type: 'certificationIssuer' }],
options: { disableNew: false }, // Allows inline creation
})
Collections use numeric order field for manual sorting:
defineField({ name: 'order', type: 'number' })
defineField({
name: 'status',
type: 'string',
options: {
list: [
{ title: 'Completed', value: 'completed' },
{ title: 'In progress', value: 'in-progress' },
// ...
],
},
})
Both project and post have publishAt: datetime for future publishing. A Sanity Function (scheduled-publish) runs every 5 minutes to promote past-due documents.
experience.computedDuration — Custom ExperienceDurationField componentpost.computedReadingTime — Custom ReadingTimeField componentsrc/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}
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
| Layer | TTL | Stale-while-revalidate | Scope |
|---|---|---|---|
| L1 (in-memory Map) | 5 min | 60s | Per-request dedup |
| L2 (Redis/Upstash) | 5 min | 60s | Cross-request |
| CDN (Sanity) | Varies | — | Edge |
When content changes in Sanity studio:
POST /api/sanity/webhook with secret headerproject → ['cms:project', 'cms:project-list'])invalidateByTag() to clear L1 + L2revalidatePath() for affected routes: /, /blog, /blog/[slug], /projects/[slug], /sitemap.xmlAll Sanity images are served through /api/media/sanity/{base64url-encoded-target}:
/api/media/sanity/{encoded}?w=1200&q=85&exp={expiresAt}&sig={hmac}SANITY_MEDIA_GATEWAY_SECRETimmutable, images get 86400simport { 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 })
/api/draft-mode/enable?secret=...pickClient() checks draftMode():
data-sanity attributes for click-to-editsanityField() from @/utils/sanity-data-attributeContent
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
studio/validation/rules.ts)| Rule | Purpose |
|---|---|
uniqueSlug | Regex + async uniqueness check |
uniqueTitle | Async title uniqueness check |
requireAltText | Alt text 4+ characters |
summaryLength | 40-280 chars |
seoTitleLength | 30-60 chars |
seoDescriptionLength | 120-160 chars |
httpsOnly | URL must be https |
dateOrder | Issue date before expiry |
endDateAfterStart | Cross-field date validation |
requiredForPublish | Conditional required fields |
maxArrayItems({max}) | Array length cap |
seoImageDimensions | Min 1200x630 pixels |
studio/schemaTypes/myType.tsstudio/schemaTypes/_registry.ts (DOCUMENT_META)studio/schemaTypes/index.tsstudio/structure/deskStructure.tsstudio/preview/previewLocations.tsstudio/templates/index.tssrc/lib/cms-content.server.tsSANITY_TYPE_TO_TAGSsrc/types/index.tsCmsContent interface: src/lib/cms-content.shared.tssrc/lib/cms-content.shared.tsstudio/scripts/backup-sanity.mjsstudio/schemaTypes/src/types/index.tssrc/lib/cms-content.server.tsCmsContent interface if needednpm run dev (studio)type: 'singleton'cms-content.server.tssrc/types/index.tsclearCmsQueryCache() or wait 5 min/api/sanity/webhook logs)type: 'document'order field for manual sorting| Command | Purpose |
|---|---|
npm run sanity:seed | Seed demo data |
studio/scripts/backup.ps1 | Export NDJSON backup (Windows) |
studio/scripts/restore.ps1 | Import NDJSON backup (Windows) |
studio/scripts/verify.ps1 | Verify backup integrity |
| Function | Trigger | Purpose |
|---|---|---|
scheduled-publish | Cron */5 * * * * | Promotes documents where publishAt has passed |
broken-refs | Cron 0 */6 * * * | Scans for broken document references |
auto-tag-images | Image asset create | Auto-tags uploaded images |
| Symptom | Likely Cause | Fix |
|---|---|---|
| Data not updating | Cache not invalidated | Check webhook secret, trigger revalidation |
| Stale content | CDN caching | Add ?t={timestamp} or clear cache |
| Draft not showing | Draft mode not enabled | Check /api/draft-mode/enable endpoint |
| Image 404 | HMAC mismatch | Check SANITY_MEDIA_GATEWAY_SECRET |
| Query returns empty | Wrong dataset or type name | Verify in Vision tool |
| Build fails | Missing env vars | Check .env.local has all required vars |
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Audit and fix accessibility issues across the portfolio to ensure WCAG 2.1 AA compliance.
日本語の概要は準備中です。原文の説明を表示しています。
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.
日本語の概要は準備中です。原文の説明を表示しています。
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.
日本語の概要は準備中です。原文の説明を表示しています。
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.
日本語の概要は準備中です。原文の説明を表示しています。
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.
日本語の概要は準備中です。原文の説明を表示しています。
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.
日本語の概要は準備中です。原文の説明を表示しています。