Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cce1588
fix: stronger black bg enforcement in Gemini image prompts
Mar 13, 2026
7428f34
release: Task 1D (engineConfig) + Task 1E (review dashboard)
codercatdev Mar 14, 2026
fa85914
temp
codercatdev Mar 17, 2026
7d9368c
refactor: consolidate monorepo with Next.js in apps/web and Sanity in…
codercatdev May 29, 2026
8d0dfc2
Merge dev: monorepo with Next.js in apps/web and Sanity in apps/sanity
codercatdev May 29, 2026
827fd8b
fix: update route type imports and adjust draft mode client configura…
codercatdev May 29, 2026
404acba
feat: update Sanity dependencies and enhance draft mode functionality
codercatdev May 29, 2026
ecbd38e
refactor: replace deprecated sanity-typegen.json with inline typegen …
codercatdev May 29, 2026
f5c9c35
chore: update dependencies for React and Next.js
codercatdev May 29, 2026
1637d9b
chore: update package dependencies and enhance sitemap functionality
codercatdev May 29, 2026
87ad666
fix: resolve Cache Components prerender errors blocking production build
codercatdev May 29, 2026
93b687c
refactor: update RSS feed generation methods for podcasts and blogs
codercatdev Jun 3, 2026
a9ae015
refactor: update cache usage in live data fetching functions
codercatdev Jun 3, 2026
648523f
refactor: enhance data fetching and caching strategies in dashboard c…
codercatdev Jun 3, 2026
7196305
feat: scaffold Astro 7 site on Cloudflare Workers
codercatdev Aug 18, 2026
4a95ece
chore: upgrade Sanity Studio to v6.9.2
codercatdev Aug 18, 2026
1b01b48
ci: add build/lint/typecheck gate and repo agent docs
codercatdev Aug 18, 2026
ed452ba
feat: Astro Sanity data layer and consolidated typegen
codercatdev Aug 18, 2026
90b26d7
feat: Astro layout, components, and listing routes
codercatdev Aug 18, 2026
4d5cdf4
feat: portable text renderers and content detail routes
codercatdev Aug 18, 2026
c5cc7a3
feat: feeds, sitemap, and robots
codercatdev Aug 18, 2026
36da187
feat: OG image generation on Workers
codercatdev Aug 18, 2026
ae87e5c
feat: Astro SSR parity, Sanity semantic search, SearchModal, and synd…
codercatdev Oct 7, 2026
bed81e7
ci: add cf-typegen step and automatic PR preview comment with Cloudfl…
codercatdev Oct 7, 2026
c82bd03
fix(ci): fix yaml indentation and use echo for pr comment markdown
codercatdev Oct 7, 2026
ce71b9c
fix(ci): fix hardcoded font paths in test mocks and enhance pr previe…
codercatdev Oct 7, 2026
011d01f
fix(ci): fix yaml indentation and clean up secrets syntax in workflows
codercatdev Oct 7, 2026
ab8cc23
feat(sanity): dedicated studio dataset workspaces and deploy workflow…
codercatdev Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
32 changes: 32 additions & 0 deletions .agents/skills/content-modeling-best-practices/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: content-modeling-best-practices
description: Structured content modeling guidance for schema design, content architecture, content reuse, references versus embedded objects, separation of concerns, and taxonomies across Sanity and other headless CMSes. Use this skill when designing or refactoring content types, deciding field shapes, debating reusable versus nested content, planning omnichannel content models, or reviewing whether a schema is too page-shaped or presentation-driven.
---

# Content Modeling Best Practices

Principles for designing structured content that's flexible, reusable, and maintainable. These concepts apply to any headless CMS but include Sanity-specific implementation notes.

## When to Apply

Reference these guidelines when:
- Starting a new project and designing the content model
- Evaluating whether content should be structured or free-form
- Deciding between references and embedded content
- Planning for multi-channel content delivery
- Refactoring existing content structures

## Core Principles

1. **Content is data, not pages** — Structure content for meaning, not presentation
2. **Single source of truth** — Avoid content duplication
3. **Future-proof** — Design for channels that don't exist yet
4. **Editor-centric** — Optimize for the people creating content

## References

Start with the reference that matches the modeling decision in front of you, instead of loading every topic at once. See `references/` for detailed guidance on specific topics:
- `references/separation-of-concerns.md` — Separating content from presentation
- `references/reference-vs-embedding.md` — When to use references vs embedded objects
- `references/content-reuse.md` — Content reuse patterns and the reuse spectrum
- `references/taxonomy-classification.md` — Flat, hierarchical, and faceted classification
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Content Reuse Patterns

Effective content models maximize reuse while minimizing duplication. Here are patterns for achieving both.

## The Content Reuse Spectrum

```
Full Duplication ←————————————————→ Full Reference
(Copy everything) (Link to one source)
```

Most real-world content sits somewhere in between.

## Pattern 1: Shared Components

Create reusable content blocks that can be embedded anywhere.

**Use case:** Testimonials, FAQs, CTAs that appear on multiple pages.

```typescript
// Standalone testimonial documents
defineType({
name: 'testimonial',
type: 'document',
fields: [
defineField({ name: 'quote', type: 'text' }),
defineField({ name: 'author', type: 'string' }),
defineField({ name: 'company', type: 'string' }),
]
})

// Reference in page builders
defineField({
name: 'pageBuilder',
type: 'array',
of: [
{ type: 'reference', to: [{ type: 'testimonial' }] }
]
})
```

## Pattern 2: Shared Field Sets

Extract common fields into reusable definitions.

**Use case:** SEO fields, social metadata, common dates.

```typescript
// Shared field definition
export const seoFields = [
defineField({ name: 'seoTitle', type: 'string' }),
defineField({ name: 'seoDescription', type: 'text' }),
defineField({ name: 'ogImage', type: 'image' }),
]

// Spread into multiple types
defineType({
name: 'page',
fields: [
defineField({ name: 'title', type: 'string' }),
...seoFields
]
})

defineType({
name: 'post',
fields: [
defineField({ name: 'title', type: 'string' }),
...seoFields
]
})
```

## Pattern 3: Taxonomy References

Centralize classification for consistent tagging.

**Use case:** Categories, tags, topics that span content types.

```typescript
// Central taxonomy
defineType({
name: 'category',
type: 'document',
fields: [
defineField({ name: 'title', type: 'string' }),
defineField({ name: 'slug', type: 'slug' }),
]
})

// Used across content types
defineField({
name: 'categories',
type: 'array',
of: [{ type: 'reference', to: [{ type: 'category' }] }]
})
```

## Pattern 4: Content Fragments

Small, reusable pieces that combine into larger content.

**Use case:** Bios, addresses, contact info.

```typescript
// Fragment type
defineType({
name: 'contactInfo',
type: 'object',
fields: [
defineField({ name: 'email', type: 'email' }),
defineField({ name: 'phone', type: 'string' }),
defineField({ name: 'address', type: 'text' }),
]
})

// Reused across types
defineType({
name: 'office',
fields: [
defineField({ name: 'name', type: 'string' }),
defineField({ name: 'contact', type: 'contactInfo' }),
]
})
```

## Anti-Pattern: Over-Abstraction

Not everything needs to be reusable. If content is only used in one place, embedding is simpler.

**Signs of over-abstraction:**
- References that are only used once
- Editors navigating multiple documents for one page
- Complex queries joining rarely-shared content
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Reference vs Embedding Content

When should content be linked (referenced) vs copied (embedded)? This decision affects reusability, query complexity, and editing workflows.

## The Trade-offs

| Aspect | Reference | Embedded Object |
|--------|-----------|-----------------|
| Reusability | ✅ Shared across documents | ❌ Copied per document |
| Single source | ✅ Update once, reflects everywhere | ❌ Must update each copy |
| Query complexity | Requires joins/expansion | Inline, simpler queries |
| Editing UX | Separate editing interface | All fields in one place |
| Independence | Can exist on its own | Only exists within parent |

## When to Reference

Use references when content:
- **Is reusable** — Same author across many articles
- **Needs central management** — Update product info once
- **Has its own lifecycle** — Published/draft independent of parent
- **Should stay in sync** — Price changes reflect everywhere

**Examples:**
- Author profiles
- Product catalog items
- Shared testimonials
- Category taxonomy
- Reusable CTAs

## When to Embed

Use embedded objects when content:
- **Is unique to this document** — Page-specific hero
- **Doesn't make sense alone** — SEO metadata
- **Should be copied, not linked** — Historical snapshot
- **Simplifies editing** — All fields in one form

**Examples:**
- SEO metadata
- Page-specific sections
- Address information
- Social links
- Configuration options

## Sanity Implementation

```typescript
// Reference: Author is reusable
defineField({
name: 'author',
type: 'reference',
to: [{ type: 'author' }]
})

// Embedded: SEO is page-specific
defineField({
name: 'seo',
type: 'object',
fields: [
defineField({ name: 'title', type: 'string' }),
defineField({ name: 'description', type: 'text' })
]
})
```

## The Hybrid Approach

Sometimes you want both: a reference for the canonical data, plus embedded overrides.

```typescript
defineField({
name: 'featuredProduct',
type: 'object',
fields: [
defineField({
name: 'product',
type: 'reference',
to: [{ type: 'product' }]
}),
defineField({
name: 'overrideTitle',
type: 'string',
description: 'Optional: Override the product title for this context'
}),
]
})
```

Query uses `coalesce(overrideTitle, product->title)`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Separation of Content and Presentation

The most important principle in structured content: **separate what content IS from how it LOOKS**.

## The Problem

When content is tied to presentation:
- Redesigns require content migration
- Content can't be reused across channels (web, mobile, voice)
- Editors make design decisions instead of content decisions
- A/B testing requires duplicate content

## The Principle

Model content based on **meaning and purpose**, not visual appearance.

### Bad: Presentation-Focused

```
BigHeroText → What if we want small heroes?
RedButton → What if brand colors change?
ThreeColumnLayout → What if mobile needs one column?
LeftSidebar → Position is a frontend concern
MobileImage → Device-specific content is fragile
```

### Good: Meaning-Focused

```
Headline → The main message (render however)
CallToAction → An action we want users to take
Features → A list of things (columns decided by frontend)
RelatedContent → Content relationships (position by context)
Image → One image with responsive crops
```

## Testing Your Model

Ask: "If we completely redesigned the site, would these field names still make sense?"

- `threeColumnFeatures` → ❌ Fails (what if 2 columns?)
- `features` → ✅ Works (describes the content's purpose: a list of product features)
- `blueHighlightBox` → ❌ Fails (what if we go purple?)
- `callout` → ✅ Works (describes the content's role: an attention-grabbing aside)

## Sanity Implementation

```typescript
// ❌ Avoid presentation-focused names
defineField({ name: 'bigHeroText', type: 'string' })
defineField({ name: 'fontSize', type: 'number' })
defineField({ name: 'backgroundColor', type: 'color' })

// ✅ Use meaning-focused names
defineField({ name: 'headline', type: 'string' })
defineField({ name: 'emphasis', type: 'string', options: { list: ['standard', 'prominent'] } })
defineField({ name: 'tone', type: 'string', options: { list: ['neutral', 'warning', 'success'] } })
```

The frontend translates `tone: 'warning'` to visual styles. Content stays semantic.
Loading
Loading