其他
开发 Sanity CMS 项目时:规范化 schema…
在基于 Sanity CMS 构建 headless CMS 时,按内容建模、GROQ 查询、TypeScript 类型、校验和预览配置来组织实现。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 40/100 ×0.15
适用场景
开发 Sanity CMS 项目时:规范特定
解决思路
- 用内容建模最佳实践设计 schema。
- 编写高效 GROQ 查询。
- 使用 TypeScript 保证类型安全。
- 按可扩展性组织项目。
- 实现正确的校验和预览。
- 按可扩展项目结构组织 Sanity 代码:schemas/documents/ 放 post.ts、author.ts;schemas/objects/ 放 blockContent.ts、image.ts;schemas/index.ts 汇总;lib/client.ts 和 lib/queries.ts 放客户端与查询;components/previews/ 放预览组件;根目录放 sanity.config.ts。
- 用 defineType、defineField 定义文档类型,例如在 schemas/documents/post.ts 定义 post。
- post 的 title 字段:type 为 string,validation 用 Rule.required().min(10).max(80)。
- post 的 slug 字段:type 为 slug,options.source 为 title,options.maxLength 为 96,validation 用 Rule.required()。
- post 的 author 字段:type 为 reference,to 为 [{ type: 'author' }],用引用建立文档关系。
- post 的 publishedAt 字段:type 为 datetime。
- post 的 body 字段:type 为 blockContent。
- post 的 preview:select title、author.name、mainImage;prepare 返回 title、subtitle(author ? `by ${author}` : '')和 media。
- 用 defineType、defineArrayMember 定义对象类型,例如在 schemas/objects/blockContent.ts 定义 blockContent 数组。
- blockContent 的 block 配置 styles:Normal/normal、H2/h2、H3/h3、Quote/blockquote。
- blockContent 的 block 配置 marks.decorators:Strong/strong、Emphasis/em、Code/code。
- blockContent 的 block 配置 marks.annotations:link 对象,含 href 字段,type 为 url。
- blockContent 允许 image 成员,并设置 options.hotspot 为 true。
- 在 lib/queries.ts 用 groq 模板编写 GROQ 查询。
- 编写 allPostsQuery:*[_type == 'post'] | order(publishedAt desc) { _id, title, slug, publishedAt, 'author': author->name, 'imageUrl': mainImage.asset->url }。
- 编写 postBySlugQuery:*[_type == 'post' && slug.current == $slug][0] { _id, title, body, publishedAt, 'author': author->{name, image}, 'categories': categories[]->title }。
- 编写 paginatedPostsQuery:*[_type == 'post'] | order(publishedAt desc) [$start...$end] { _id, title, slug, excerpt }。
- 编写 conditionalQuery:*[_type == 'post'] { title, 'content': select(defined(body) => body, 'No content available') }。
- 编写 fallbackQuery:*[_type == 'post'] { 'displayTitle': coalesce(seoTitle, title) }。
- 编写 withReferencesQuery:*[_type == 'post' && references($authorId)] { title, publishedAt }。
- 在 lib/client.ts 从 @sanity/client 导入 createClient,从 @sanity/image-url 导入 imageUrlBuilder。
- 用 createClient 创建 client: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'。
- 用 imageUrlBuilder(client) 创建 builder,并导出 urlFor(source),返回 builder.image(source)。
- 从 schema 生成 TypeScript 类型,并从 '@/sanity/types' 导入 Post、Author。
- 实现 getPosts(): Promise<Post[]>,返回 client.fetch(allPostsQuery)。
- 实现 getPost(slug: string): Promise<Post | null>,返回 client.fetch(postBySlugQuery, { slug })。
- 在 defineField 中配置 validation;例如 email 字段用 Rule.required().email().custom(...),当 email 不以 '@company.com' 结尾时返回 'Must be a company email'。
- 用 StringInputProps 编写自定义输入组件 CustomStringInput:渲染 label 为 props.schemaType.title,调用 props.renderDefault(props),显示 props.value?.length ?? 0 characters。
- 使用 references 处理文档间关系。
- 实现合适的校验规则。
- 创建有意义的预览配置。
- 使用 portable text 处理富内容。
- 使用 Sanity 图片管线优化图片。
- 设置正确的 CORS 和 API 权限。
适用边界 · 注意事项
- 不使用 Sanity CMS 或不需要 headless CMS 时,不要套用其 schema、GROQ 和客户端配置。
- 如果内容很少且不需要富文本、图片管线、预览或权限配置,直接用静态文件或简单数据库更合适。
- 没有 Sanity 项目 ID、dataset、API 权限和 CORS 配置时,无法完成客户端调用。