sanity

安装量: 91
排名: #8864

安装

npx skills add https://github.com/mindrally/skills --skill sanity

Sanity CMS Development

You are an expert in Sanity CMS, GROQ queries, TypeScript integration, and headless CMS architecture.

Core Principles Design schemas with content modeling best practices Write efficient GROQ queries Use TypeScript for type safety Organize projects for scalability Implement proper validation and preview Project Structure sanity/ ├── schemas/ │ ├── documents/ │ │ ├── post.ts │ │ └── author.ts │ ├── objects/ │ │ ├── blockContent.ts │ │ └── image.ts │ └── index.ts ├── lib/ │ ├── client.ts │ └── queries.ts ├── components/ │ └── previews/ └── sanity.config.ts

Schema Definition Document Types // schemas/documents/post.ts import { defineType, defineField } from 'sanity';

export default defineType({ name: 'post', title: 'Post', type: 'document', fields: [ defineField({ name: 'title', title: 'Title', type: 'string', validation: (Rule) => Rule.required().min(10).max(80), }), defineField({ name: 'slug', title: 'Slug', type: 'slug', options: { source: 'title', maxLength: 96, }, validation: (Rule) => Rule.required(), }), defineField({ name: 'author', title: 'Author', type: 'reference', to: [{ type: 'author' }], }), defineField({ name: 'publishedAt', title: 'Published at', type: 'datetime', }), defineField({ name: 'body', title: 'Body', type: 'blockContent', }), ], preview: { select: { title: 'title', author: 'author.name', media: 'mainImage', }, prepare({ title, author, media }) { return { title, subtitle: author ? by ${author} : '', media, }; }, }, });

Object Types // schemas/objects/blockContent.ts import { defineType, defineArrayMember } from 'sanity';

export default defineType({ name: 'blockContent', title: 'Block Content', type: 'array', of: [ defineArrayMember({ type: 'block', styles: [ { title: 'Normal', value: 'normal' }, { title: 'H2', value: 'h2' }, { title: 'H3', value: 'h3' }, { title: 'Quote', value: 'blockquote' }, ], marks: { decorators: [ { title: 'Strong', value: 'strong' }, { title: 'Emphasis', value: 'em' }, { title: 'Code', value: 'code' }, ], annotations: [ { name: 'link', type: 'object', title: 'URL', fields: [ { name: 'href', type: 'url', title: 'URL', }, ], }, ], }, }), defineArrayMember({ type: 'image', options: { hotspot: true }, }), ], });

GROQ Queries Basic Queries // lib/queries.ts

// Get all posts export const allPostsQuery = groq*[_type == "post"] | order(publishedAt desc) { _id, title, slug, publishedAt, "author": author->name, "imageUrl": mainImage.asset->url };

// Get single post by slug export const postBySlugQuery = groq*[_type == "post" && slug.current == $slug][0] { _id, title, body, publishedAt, "author": author->{name, image}, "categories": categories[]->title };

// Pagination export const paginatedPostsQuery = groq*[_type == "post"] | order(publishedAt desc) [$start...$end] { _id, title, slug, excerpt };

Advanced GROQ // Conditional projections export const conditionalQuery = groq*[_type == "post"] { title, "content": select( defined(body) => body, "No content available" ) };

// Coalesce for fallbacks export const fallbackQuery = groq*[_type == "post"] { "displayTitle": coalesce(seoTitle, title) };

// References export const withReferencesQuery = groq*[_type == "post" && references($authorId)] { title, publishedAt };

Client Setup // lib/client.ts import { createClient } from '@sanity/client'; import imageUrlBuilder from '@sanity/image-url';

export const client = createClient({ projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!, dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!, apiVersion: '2024-01-01', useCdn: process.env.NODE_ENV === 'production', });

const builder = imageUrlBuilder(client);

export function urlFor(source: any) { return builder.image(source); }

TypeScript Integration // Generate types from schema import { Post, Author } from '@/sanity/types';

export async function getPosts(): Promise { return client.fetch(allPostsQuery); }

export async function getPost(slug: string): Promise { return client.fetch(postBySlugQuery, { slug }); }

Validation defineField({ name: 'email', type: 'string', validation: (Rule) => Rule.required() .email() .custom((email) => { if (email && !email.endsWith('@company.com')) { return 'Must be a company email'; } return true; }), });

Custom Components // Custom input component import { StringInputProps } from 'sanity';

export function CustomStringInput(props: StringInputProps) { return (

{props.renderDefault(props)} {props.value?.length ?? 0} characters
); }

Best Practices Use references for relationships between documents Implement proper validation rules Create meaningful preview configurations Use portable text for rich content Optimize images with Sanity's image pipeline Set up proper CORS and API permissions

返回排行榜