VICTOR
DAJ
Next.js Expert
Headless CMS
Content Architecture

Let's talk

Tell me about your project

Back to Blog
CMS

Content Modeling Best Practices for Headless CMS

How to structure your content for maximum flexibility and reusability—lessons learned from building content architectures at scale.

October 28, 2025 · 9 min read
Content Modeling Best Practices for Headless CMS

Content Modeling Best Practices for Headless CMS

The difference between a good headless CMS implementation and a great one often comes down to content modeling. Get it right, and your content scales beautifully. Get it wrong, and you're refactoring for months.

Think in Blocks, Not Pages

The biggest mindset shift in headless CMS: content is not pages.

Instead of creating a "Homepage" content type with every field hardcoded, create reusable blocks:

// ❌ Bad: Page-centric model
const Homepage = {
  heroTitle: "string",
  heroSubtitle: "string",
  heroImage: "image",
  feature1Title: "string",
  feature1Description: "string",
  feature2Title: "string",
  // ... endless fields
};

// ✅ Good: Block-based model
const HeroBlock = {
  title: "string",
  subtitle: "string",
  image: "image",
  cta: "reference(CTA)",
};

const FeatureBlock = {
  title: "string",
  description: "richText",
  icon: "string",
};

const Page = {
  title: "string",
  slug: "slug",
  blocks: "array(HeroBlock | FeatureBlock | ...)",
};

Normalize Your References

Don't duplicate data—reference it:

// ❌ Bad: Duplicated author data
const Post = {
  title: "string",
  authorName: "string",
  authorBio: "string",
  authorImage: "image",
};

// ✅ Good: Referenced author
const Author = {
  name: "string",
  bio: "text",
  image: "image",
  social: "object",
};

const Post = {
  title: "string",
  author: "reference(Author)",
};

Plan for Localization Early

Even if you're not multi-language now, structure for it:

// Sanity example with localization
const Post = {
  title: {
    type: "localeString", // Custom type
    // Expands to: { en: 'string', es: 'string', fr: 'string' }
  },
  content: {
    type: "localePortableText",
  },
};

SEO as a Reusable Object

Create a shared SEO object used across content types:

const SEO = {
  metaTitle: "string",
  metaDescription: "text",
  ogImage: "image",
  noIndex: "boolean",
};

const Page = {
  // ... other fields
  seo: "object(SEO)",
};

const Post = {
  // ... other fields
  seo: "object(SEO)",
};

Content Relationships Matrix

Before building, map out your relationships:

Content Type References Referenced By
Author - Post, Page
Category - Post
Post Author, Category Page (featured)
Page Post, Author -
CTA - HeroBlock, Page

Validation Rules

Set constraints at the model level:

// Sanity validation example
const Post = defineType({
  name: "post",
  fields: [
    {
      name: "title",
      type: "string",
      validation: (Rule) => Rule.required().max(60),
    },
    {
      name: "excerpt",
      type: "text",
      validation: (Rule) => Rule.required().max(160),
    },
    {
      name: "slug",
      type: "slug",
      options: { source: "title" },
      validation: (Rule) => Rule.required(),
    },
  ],
});

The Migration Trap

Plan your content model thoroughly before importing content. Migrating a bad model with thousands of documents is painful.

My process:

  1. Sketch content types on paper
  2. Build in CMS with sample content
  3. Test all query patterns
  4. Import real content only when confident

Key Takeaways

  • Blocks over pages: Maximum reusability
  • Normalize references: Single source of truth
  • Plan for i18n: Even if not needed now
  • Validate early: Catch issues at input, not output
  • Document everything: Future you will thank you

Share on

Victor Daj

Victor Daj

October 28, 2025

Free Course

Learn Headless CMS

Get instant access • 30 min • No fluff

No spam. Unsubscribe anytime.