Design-to-CMS automation locks you into inflexible content structures. Here's why the separation matters and how to do it right.
Knut Melvær
Head of Developer Community and Education
Published
Why design-driven content modeling creates technical debt, not velocity
You know that moment when your designer updates a Figma component and suddenly your content model breaks in production? No? Consider yourself lucky.
A headless CMS vendor just announced their Figma connector at their first conference. Others have been pushing "visual development" workflows. The pitch is seductive: push a button in Figma, get content types in your CMS. "Eliminate hours of manual work!" The demos are slick. Watch a component magically transform into schema fields.
But here's what they don't show in the demo: developers still need to build every frontend component, write every query, and maintain every connection. You haven't eliminated developer work. You've just moved it downstream where it's harder to fix.
This is like writing inline styles instead of CSS classes. Sure, you can see exactly what you're styling right there in the markup. But good luck maintaining it, reusing it, or changing your .
What Storyblok literally shows in their demo: designers creating content types without understanding your codebase, your data patterns, or your API contracts. They're celebrating saving "hours" on schema creation, as if the problem with content modeling was the typing, not the thinking. Meanwhile, your developers now have to build components that consume whatever structure got auto-generated, whether it makes sense or not.
Here's what actually happens when you let design tools dictate your content model:
Now multiply this by every component variant, every A/B test, every seasonal campaign. Your designers are doing exactly what they should: versioning and organizing their work. But that organizational system was never meant to become your data model.
Your content team wants to update a product description once and have it appear everywhere. Your developers want clean, predictable data structures. Your AI tools need semantic understanding of content relationships. None of these stakeholders care about the 40px padding on your hero section.
Content operations at scale require:
Design-driven modeling violates all of these principles. You're creating single-purpose content types tied to specific visual presentations. That "TestimonialCard_v2" type becomes useless when you redesign the testimonials section next quarter.
Let's trace what happens in a typical organization using design-to-CMS mapping:
Month 1: "This workflow is amazing! Designers can ship landing pages without dev involvement!"
Month 2: Developers discover they're coding against content types like HeroWithTwoButtonsLeft and HeroWithOneButtonCentered. Every design variation has become a unique API contract they have to implement.
Month 3: You have 47 content types. Half are variations of the same concept. TeamMember, StaffCard, AboutUsProfile, and PersonnelBio all represent the same thing: a person.
Month 6: Content editors are copy-pasting between similar-but-not-identical types. The "Meet Our Team" page uses different data than the author bylines, which differ from the conference speaker profiles.
Month 12: A rebrand requires updating 200+ components. Since each maps to a content type with production data, migration scripts multiply like rabbits. Your "velocity gain" has become a refactoring nightmare.
There's exactly one scenario where component-to-content mapping makes sense:
If you're building one-off landing pages that will be deleted after the campaign, go wild. For everyone else, there's a better way.
You might think page builders are the perfect use case for design-driven content types. After all, aren't they literally about assembling visual components?
Not quite. Even in page builder scenarios, you need real content modeling:
// What the design-first approach gives you
type HeroBlock = {
heading: string
image: image
cta: string
}
type TeamBlock = {
title: string
members: Array<{
name: string
role: string
photo: image
}>
}
// What you actually need
type HeroBlock = {
heading: string
image: image
cta: string
featuredProduct?: Reference<Product> // Pull in product data
campaign?: Reference<Campaign> // Track which campaign
}
type TeamBlock = {
title: string
members: Array<Reference<Person>> // Reuse your People data
department?: Reference<Department> // Connect to org structure
}Even page builders benefit from proper content architecture. That team member who appears in your "About Us" page builder block? They should be the same Person document that appears in blog post bylines, conference speaker lists, and email signatures.
That product featured in your hero? It should pull from your product catalog, not duplicate the data. When the price changes or it goes out of stock, you want that reflected everywhere automatically.
Page builders aren't an excuse to abandon content modeling. They're just another presentation layer that should consume your properly structured content.
Here's how teams successfully balance design velocity with content operations:
For design-to-content workflows to even remotely work at scale, you need military-grade governance of your design system. Not just "keep components tidy" governance, but:
Ask yourself: Has your design team ever renamed a component from HeaderNav to PrimaryNavigation for clarity? Congratulations, you just broke production. Did someone create TeamMemberCardCompact because the original TeamMemberCard didn't fit the new layout? You now have duplicate content types that will haunt you forever.
This isn't how design teams work, and it shouldn't be. Designers need freedom to explore, iterate, and respond to user feedback without worrying about database migrations.
The truth is, it's far easier to use your content model to inspire your design system. Here's why:
When you start with a content model, you're starting with the business domain. A Person is a person whether they appear in an author byline, team page, or conference speaker list. A Product has the same core attributes whether it's in a hero banner or a comparison table.
Your design system can create infinite presentations of the same content. But the content structure remains stable, reusable, and semantically clear.
In organizations where design, development, and content teams collaborate, you need stable interfaces between disciplines. The content model is that interface.
When the content model is stable:
When design drives content structure:
Here's what this looks like in practice:
// Monday: Designer creates new author component variant
DesignSystem.AuthorCardMinimal
// Tuesday: Auto-generates new content type
type AuthorCardMinimal = {
authorName: string
authorTitle: string
// Missing: bio, social links, department
}
// Wednesday: Content team confused
"Do I update Person, Author, or AuthorCardMinimal?"
// Thursday: Developer discovers the mess
"We now have 3 types representing the same person"
// Friday: Emergency meeting to "align on governance"
// (Spoiler: it won't work)This problem gets worse with agency handoffs. An agency builds a beautiful design system in Figma, pushes it to your CMS, and leaves. Six months later:
HeroBannerV2Final exists alongside HeroSectionUpdatedCompare this to content-model-first handoffs:
// Agency receives your content model
interface ContentModel {
products: Product[]
people: Person[]
articles: Article[]
// Clear, stable, documented
}
// Agency builds components that consume it
// They can be creative without breaking your dataThe agency can completely reimagine the presentation without touching your content structure. When they leave, you can hire anyone to continue the work. The content model is the contract, and contracts need to be stable.
With Sanity, your content model lives in version control, right next to your design system. Both are code, both are versioned, both can evolve. But here's the key: they evolve independently.
// Your stable content model (v1.0.0)
export const person = defineType({
name: 'person',
fields: [
defineField({ name: 'name', type: 'string' }),
defineField({ name: 'role', type: 'string' }),
defineField({ name: 'department', type: 'reference' })
]
})
// Your evolving design system (v47.3.2)
// Can change daily without touching content structure
export const TeamCard = { /* ... */ }
export const TeamCardCompact = { /* ... */ }
export const TeamCardMinimal = { /* ... */ }
export const TeamCardWithHoverState = { /* ... */ }Designers can ship new variants every sprint. Developers can refactor components every day. The content model remains your stable foundation. This is only possible when your schema is code, not something clicked together in a UI that auto-generates from Figma.
// Instead of this presentation-focused model
type HeroSection = {
largeHeadline: string
smallSubtext: string
leftImage: image
rightCTA: button
}
// Build this domain model
type Product = {
name: string
description: text
features: string[]
media: image[]
pricing: Price
}
// Then compose it in your UI
<Hero
headline={product.name}
description={product.description}
image={product.media[0]}
/>The Figma MCP server plus modern AI coding assistants show a smarter pattern. Instead of pushing component structures into your CMS, you can:
// With Figma MCP + Claude/Cursor:
// 1. AI reads your Figma component structure
// 2. AI knows your existing content model
// 3. AI generates the bridge code
const component = await generateComponent({
context: {
figmaComponent: 'HeroSection', // From Figma MCP
contentModel: 'product', // Your existing schema
framework: 'react' // Your tech stack
}
})
// Result: Frontend code that connects design to data
// without polluting your content modelYour content model stays clean. Your design system stays flexible. Your developers stay sane.
Smart teams build an abstraction between design components and content types:
// Design components reference semantic content
type ComponentProps = {
content: {
primary: ContentBlock
secondary?: ContentBlock
media?: MediaAsset
}
variant: 'hero' | 'card' | 'banner'
}
// Content stays portable
type ContentBlock = {
title: string
body: PortableText
references: Reference[]
}This way, design can evolve independently from content structure. When you redesign, you're updating the presentation layer, not migrating data.
Here's how modern teams are using AI tools to bridge design and content without creating technical debt:
// 1. Designer creates component in Figma
// 2. Developer uses Figma MCP + Claude/Cursor to read the design
// In your AI coding assistant with Figma MCP:
"Read the HeroSection component from Figma and generate
a React component that uses our existing 'product' content type"
// The AI understands both contexts:
// - Your Figma component structure
// - Your existing Sanity schema
// Generated component maps design to your domain model:
export function HeroSection({ product }) {
return (
<section className={styles.hero}>
<h1>{product.name}</h1>
<p>{product.tagline}</p>
<Image
src={product.media?.[0]}
alt={product.name}
/>
</section>
)
}
// The GROQ query stays clean:
const query = `*[_type == "product" && featured == true][0]{
name,
tagline,
media
}`This isn't about building another abstraction layer or plugin. It's about using AI context to translate between design intent and existing content structure. The Figma MCP server gives your AI assistant visibility into the design system, while your codebase provides the content model context.
The key difference: Figma informs the component creation, but your content model drives the data structure. No new tools, no complex mappings. Just intelligent code generation that respects both design and data.
The tools pushing automatic Figma-to-CMS conversion want you to believe that removing the developer from the loop increases velocity. But what they're really doing is removing the only person who understands the downstream implications.
With AI-assisted development (Figma MCP + Claude/Cursor):
The "automatic" approach gives you speed today and migration scripts forever. The AI-assisted approach gives you sustainable velocity.
FooterLinks, NavbarItem)HeroV2, CardV3Final)If you've already gone down this path, here's your escape route:
Look, we get it. The pressure to ship fast is real. When a vendor shows you a button that turns Figma components into content types, it feels like found time.
But here's the thing: you're not actually saving developer time. You're just moving it. Instead of spending an hour thinking through your content model upfront, you'll spend weeks untangling it later. Instead of one developer reviewing schema changes, you'll have three developers debugging why the same content exists in five different types.
The companies pushing design-to-content workflows have built a solution to a problem that mostly exists because of architectural choices. If your content model is trapped in a UI, yeah, you need magic buttons to generate it. But if your content model is code—versioned, reviewed, and living right next to your components—you don't need the automation. You need good architecture.
Design tools should accelerate how you present content. Your content model should represent your business domain. The bridge between them? That's what developers are for. And honestly, with AI coding assistants that understand both your Figma components and your existing schema, building that bridge is faster than ever.
Just don't let the bridge become your foundation.
---
Want to talk through your content architecture before you end up with 47 types representing the same concept. .
// What the design-first approach gives you
type HeroBlock = {
heading: string
image: image
cta: string
}
type TeamBlock = {
title: string
members: Array<{
name: string
role: string
photo: image
}>
}
// What you actually need
type HeroBlock = {
heading: string
image: image
cta: string
featuredProduct?: Reference<Product> // Pull in product data
campaign?: Reference<Campaign> // Track which campaign
}
type TeamBlock = {
title: string
members: Array<Reference<Person>> // Reuse your People data
department?: Reference<Department> // Connect to org structure
}// Monday: Designer creates new author component variant
DesignSystem.AuthorCardMinimal
// Tuesday: Auto-generates new content type
type AuthorCardMinimal = {
authorName: string
authorTitle: string
// Missing: bio, social links, department
}
// Wednesday: Content team confused
"Do I update Person, Author, or AuthorCardMinimal?"
// Thursday: Developer discovers the mess
"We now have 3 types representing the same person"
// Friday: Emergency meeting to "align on governance"
// (Spoiler: it won't work)// Agency receives your content model
interface ContentModel {
products: Product[]
people: Person[]
articles: Article[]
// Clear, stable, documented
}
// Agency builds components that consume it
// They can be creative without breaking your data// Your stable content model (v1.0.0)
export const person = defineType({
name: 'person',
fields: [
defineField({ name: 'name', type: 'string' }),
defineField({ name: 'role', type: 'string' }),
defineField({ name: 'department', type: 'reference' })
]
})
// Your evolving design system (v47.3.2)
// Can change daily without touching content structure
export const TeamCard = { /* ... */ }
export const TeamCardCompact = { /* ... */ }
export const TeamCardMinimal = { /* ... */ }
export const TeamCardWithHoverState = { /* ... */ }// Instead of this presentation-focused model
type HeroSection = {
largeHeadline: string
smallSubtext: string
leftImage: image
rightCTA: button
}
// Build this domain model
type Product = {
name: string
description: text
features: string[]
media: image[]
pricing: Price
}
// Then compose it in your UI
<Hero
headline={product.name}
description={product.description}
image={product.media[0]}
/>// Design components reference semantic content
type ComponentProps = {
content: {
primary: ContentBlock
secondary?: ContentBlock
media?: MediaAsset
}
variant: 'hero' | 'card' | 'banner'
}
// Content stays portable
type ContentBlock = {
title: string
body: PortableText
references: Reference[]
}// With Figma MCP + Claude/Cursor:
// 1. AI reads your Figma component structure
// 2. AI knows your existing content model
// 3. AI generates the bridge code
const component = await generateComponent({
context: {
figmaComponent: 'HeroSection', // From Figma MCP
contentModel: 'product', // Your existing schema
framework: 'react' // Your tech stack
}
})
// Result: Frontend code that connects design to data
// without polluting your content model// 1. Designer creates component in Figma
// 2. Developer uses Figma MCP + Claude/Cursor to read the design
// In your AI coding assistant with Figma MCP:
"Read the HeroSection component from Figma and generate
a React component that uses our existing 'product' content type"
// The AI understands both contexts:
// - Your Figma component structure
// - Your existing Sanity schema
// Generated component maps design to your domain model:
export function HeroSection({ product }) {
return (
<section className={styles.hero}>
<h1>{product.name}</h1>
<p>{product.tagline}</p>
<Image
src={product.media?.[0]}
alt={product.name}
/>
</section>
)
}
// The GROQ query stays clean:
const query = `*[_type == "product" && featured == true][0]{
name,
tagline,
media
}`