This commit is contained in:
@@ -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
|
||||
@@ -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)`.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,136 @@
|
||||
# Taxonomy and Classification
|
||||
|
||||
Organizing content with taxonomies enables filtering, navigation, and content relationships. Well-designed taxonomies scale; poorly designed ones become maintenance nightmares.
|
||||
|
||||
## Types of Classification
|
||||
|
||||
### Flat Taxonomy
|
||||
Simple list of terms with no hierarchy.
|
||||
|
||||
**Use for:** Tags, simple categories
|
||||
**Example:** Blog tags: "javascript", "react", "tutorial"
|
||||
|
||||
```typescript
|
||||
defineType({
|
||||
name: 'tag',
|
||||
type: 'document',
|
||||
fields: [
|
||||
defineField({ name: 'title', type: 'string' }),
|
||||
defineField({ name: 'slug', type: 'slug' }),
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### Hierarchical Taxonomy
|
||||
Terms with parent-child relationships.
|
||||
|
||||
**Use for:** Product categories, content sections
|
||||
**Example:** Electronics > Phones > Smartphones
|
||||
|
||||
```typescript
|
||||
defineType({
|
||||
name: 'category',
|
||||
type: 'document',
|
||||
fields: [
|
||||
defineField({ name: 'title', type: 'string' }),
|
||||
defineField({ name: 'slug', type: 'slug' }),
|
||||
defineField({
|
||||
name: 'parent',
|
||||
type: 'reference',
|
||||
to: [{ type: 'category' }],
|
||||
description: 'Parent category (leave empty for top-level)'
|
||||
}),
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### Faceted Classification
|
||||
Multiple independent dimensions.
|
||||
|
||||
**Use for:** Complex filtering (e-commerce)
|
||||
**Example:** Filter by color AND size AND price range
|
||||
|
||||
```typescript
|
||||
// Multiple taxonomy types
|
||||
defineField({ name: 'color', type: 'reference', to: [{ type: 'color' }] })
|
||||
defineField({ name: 'size', type: 'reference', to: [{ type: 'size' }] })
|
||||
defineField({ name: 'material', type: 'reference', to: [{ type: 'material' }] })
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
|
||||
### 1. Mutual Exclusivity (When Appropriate)
|
||||
Categories should be distinct. If items frequently belong to multiple categories, consider tags instead.
|
||||
|
||||
**Categories:** One primary classification
|
||||
**Tags:** Many optional classifications
|
||||
|
||||
### 2. User-Centric Naming
|
||||
Use terms your audience uses, not internal jargon.
|
||||
|
||||
**Bad:** "Content Assets" (internal term)
|
||||
**Good:** "Resources" or "Downloads" (user term)
|
||||
|
||||
### 3. Balanced Depth
|
||||
Too shallow: Everything lumped together
|
||||
Too deep: Users can't find anything
|
||||
|
||||
**Rule of thumb:** 3-4 levels max for hierarchies
|
||||
|
||||
### 4. Scalable Structure
|
||||
Design for 10x growth. Will your structure work with 10,000 items?
|
||||
|
||||
## Querying Taxonomies
|
||||
|
||||
### Get all items in a category
|
||||
|
||||
```groq
|
||||
*[_type == "product" && category._ref == $categoryId]
|
||||
```
|
||||
|
||||
### Get items in category OR children
|
||||
|
||||
```groq
|
||||
// First get all descendant category IDs
|
||||
*[_type == "product" && category._ref in
|
||||
*[_type == "category" && (
|
||||
_id == $categoryId ||
|
||||
parent._ref == $categoryId ||
|
||||
parent->parent._ref == $categoryId
|
||||
)]._id
|
||||
]
|
||||
```
|
||||
|
||||
### Get category tree
|
||||
|
||||
```groq
|
||||
*[_type == "category" && !defined(parent)]{
|
||||
title,
|
||||
slug,
|
||||
"children": *[_type == "category" && parent._ref == ^._id]{
|
||||
title,
|
||||
slug,
|
||||
"children": *[_type == "category" && parent._ref == ^._id]{
|
||||
title,
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### Over-categorization
|
||||
Creating a category for everything results in mostly-empty categories.
|
||||
|
||||
**Fix:** Start minimal, add categories as content grows.
|
||||
|
||||
### Inconsistent Granularity
|
||||
Some categories broad ("Technology"), others narrow ("React 18 Server Components").
|
||||
|
||||
**Fix:** Define clear criteria for category creation.
|
||||
|
||||
### No Governance
|
||||
Anyone can create taxonomy terms, leading to duplicates and inconsistency.
|
||||
|
||||
**Fix:** Limit who can create/edit taxonomy documents. Use validation.
|
||||
Reference in New Issue
Block a user