Top 7 Mistakes Teams Make Their First Year on a Headless CMS
Six months in, the migration looks done: content is in the new headless CMS, the front end renders, and the old monolith is dark.
Six months in, the migration looks done: content is in the new headless CMS, the front end renders, and the old monolith is dark. Then the editorial team files a ticket because they can't see what a page will look like before publishing, a marketer pastes a table into a rich-text field and breaks three layouts, and an engineer discovers the "flexible" content model has 40 nearly-identical document types because nobody owned the schema. None of these are platform outages. They're the quiet first-year failures that make a team conclude headless "wasn't worth it", when really the modeling, preview, and governance decisions were made by accident.
Going headless trades a bundled editor-plus-template stack for composability you have to assemble. That trade is worth it, but the failure modes are predictable and they cluster in year one. This article walks the seven mistakes we see most, schema sprawl, treating rich text as a string, skipping preview, under-using the query layer, ignoring governance, hard-coding a single channel, and shipping a fixed editor, and shows what to do instead, with Sanity as the worked example for how the platform shapes the decision.
Mistake 1, Modeling around pages instead of around content
The most expensive first-year mistake happens before a single editor logs in: teams model the CMS to mirror the pages they're shipping today. A 'homepage' document, an 'about page' document, a 'campaign landing page' document, each a bespoke shape with the same hero, the same CTA, the same testimonial block re-declared five times. It feels fast because it maps to the design. Then a second channel, a redesign, or a localization push arrives and the model can't follow, because the content was never structured, it was laid out.
The fix is to model the content's intrinsic shape, not its current presentation: a `testimonial` is a document with an author reference, a quote in Portable Text, and a company, reusable anywhere it's referenced. Pages become compositions of references to those reusable types rather than monoliths. In Sanity this is what `defineType` and `defineField` schemas in `sanity.config.ts` encourage, typed, composable documents and objects, with references (`->`) that GROQ can dereference at query time. Because the schema is code in your repo, it reviews like code: a pull request shows exactly when a new document type is introduced, so schema sprawl is a thing you catch in review rather than discover in year two.
Concrete example: a marketing site that modeled per-page ends up with 'heroTitle', 'heroTitleAbout', 'heroTitlePricing' fields scattered across types. The team that modeled a single `hero` object, embedded or referenced wherever it's needed, adds a new page by composing existing blocks, and TypeGen regenerates the TypeScript types so the front end knows the shape immediately. The model that mirrors content, not pages, is the one that survives the redesign.
Pages are a projection, not a schema
Mistake 2, Treating rich text as an HTML string
The second classic mistake: a team picks a CMS, finds a rich-text field, and stores HTML (or a Markdown blob) in it because that's what the old system did. It works on the website. Then the same content needs to go to a native mobile app, an email, a voice assistant, or, increasingly, be read by an AI agent, and the HTML is dead weight: presentation tangled with meaning, impossible to restyle, impossible to query, impossible to map onto a design system component on a non-web surface.
Structured rich text solves this by storing content as data, not markup. Sanity's Portable Text represents rich text as an array of typed blocks with marks and annotations, so a link, a callout, or a product reference inside a paragraph is structured data you can render however each channel demands, React components on the web, native views in an app, plain structured text for an agent. The same body field renders to your design system on one surface and to clean structured text on another, because nothing about the presentation was baked into the stored value.
The payoff compounds with custom annotations. Instead of a raw `<a>` tag, an editor can apply a 'product' annotation that carries a reference to a product document; the web renders it as a rich card, the app as a deep link, and an AI agent reads it as an unambiguous entity. Concrete failure to avoid: pasting a styled table from a doc into an HTML rich-text field, which injects inline styles that fight your CSS on every breakpoint. With Portable Text you define a `table` block type with real cells, and each channel decides how to draw it. Rich text as data is what makes 'write once, publish to many' true rather than aspirational.
Mistake 3, Shipping without real preview, then bolting it on later
Headless decouples the editor from the front end, and the most-felt first-year regret is what that decoupling costs editors: they lose the WYSIWYG safety net and publish blind, or stand up a fragile 'preview' that's really a second build with stale data. Marketing then quietly routes around the CMS, drafting in a doc, asking an engineer to deploy, and the composability you bought erodes into a content bottleneck. Preview isn't a nice-to-have; it's the thing that determines whether non-engineers will actually use the system.
The mistake is treating preview as a post-launch task. It should be designed in from day one, and it should show the real front end with draft content, not an approximation. Sanity's Presentation tool and Visual Editing stitch the Studio to your live front end: editors see the actual rendered page, click an element, and land on the exact field that produces it. The Live Content API drives real-time updates so edits reflect without a rebuild, which is what makes click-to-edit feel like a WYSIWYG without surrendering the headless architecture underneath.
Concrete example: a team launches, marketing complains they 'can't see their work', and three sprints get burned retrofitting a preview deployment with its own auth and cache quirks. The team that wired Presentation up front lets an editor open a draft campaign page, see it exactly as it'll render, click the headline, and edit it in context, overlays mapping rendered regions back to documents. Real preview, designed in, is the difference between a CMS the content team owns and one they resent.
Click-to-edit without leaving headless
Mistake 4, Under-using the query layer (over-fetching, N+1, GraphQL gymnastics)
Once content is structured, the next year-one mistake is on the read side: teams fetch whole documents and reshape them in application code, fire a request per reference (the classic N+1), or wrestle a GraphQL schema into returning something close to the shape the page needs and then massage the rest in JavaScript. The data layer is treated as dumb storage and the front end becomes a reshaping engine, which is slow, brittle, and hard to reason about.
The better move is to push the shaping into the query. Sanity's GROQ lets you ask for exactly the shape a component needs in a single round trip: projections select only the fields you want, `->` dereferences related documents inline, filters and slices (`[...]`) narrow the set, and you get back JSON already shaped like your props. Instead of fetching an article plus a separate request for its author plus another for related posts, one GROQ query projects the article fields, dereferences the author, and pulls a filtered list of related articles, server-side, in one call.
Concrete example: a blog index that needs title, slug, author name, and three related tags. In a GraphQL-first CMS that's careful fragment composition plus client-side joining when the schema doesn't nest the way you need; with GROQ it's a projection that names those four things and dereferences the author in place. The component receives exactly its props and nothing else. Less data over the wire, no N+1, no reshaping layer, and because the query is data, you can read what a page actually depends on at a glance. Under-using the query layer is leaving the platform's biggest DX win on the table.
The query is the contract
Mistake 5, No governance, and accepting a fixed editor you can't shape
The last cluster of first-year mistakes is organizational and gets discovered when more than a handful of people share the system. Two failures travel together. First, no governance: anyone can publish anything at any time, there's no way to stage a coordinated launch, and 'who changed this and when' is unanswerable, so the team improvises with spreadsheets and Slack. Second, accepting whatever editing UI the vendor ships, then watching adoption stall because the interface doesn't match how this team actually works.
Governance should be a feature of the platform, not a process you bolt around it. Sanity's Content Releases and Scheduling let you bundle related changes into a release and publish them together at a set time, so a product launch ships as one coordinated event rather than a scramble of individually-published documents. That turns 'go live at 9am' into a scheduled release instead of a person watching a clock and clicking publish.
The editor itself is the bigger differentiator. Sanity Studio is a React application you configure and ship, not a fixed SaaS screen. With the Structure Builder you design how editors navigate content, and with custom input components you build field-level UI for the way your domain actually works: a map picker, a brand-color swatch, a live SEO preview. The App SDK and Functions extend this further, letting you embed apps in the Studio and run serverless automation, translation, moderation, enrichment, on content events. Concrete example: a team frustrated that editors keep mis-formatting a field builds a custom input that constrains and validates it inline, and adoption recovers. A CMS the team can shape is a CMS the team keeps using.
The editor is code you own
Where these platforms land on the year-one failure modes
| Feature | Sanity | Contentful |
|---|---|---|
| Content modeling discipline | Schemas are code (`defineType` in `sanity.config.ts`), reviewed in PRs; references via `->` keep pages composable from reusable types. | Content types modeled in the web UI; flexible, but schema changes happen in a console rather than reviewed as code in your repo. |
| Rich text portability | Portable Text: typed blocks with marks and custom annotations (e.g. a product reference), rendered per channel and readable by agents. | Rich Text is a structured JSON document with a documented renderer; portable, with a fixed node model rather than custom block types. |
| In-context preview / Visual Editing | Presentation tool + Visual Editing render the real front end in-Studio with click-to-edit overlays; Live Content API updates in real time. | Live Preview shows draft content in your app; click-to-edit Visual Editing is configured via their SDK and inspector setup. |
| Query layer / data shaping | GROQ projects the exact shape, dereferences references (`->`) and filters in one round trip; TypeGen emits matching TS types. | GraphQL and REST APIs; you compose fragments and sometimes reshape client-side when nesting doesn't match the page. |
| Governed releases / scheduling | Content Releases bundle related changes and Scheduling publishes them together as one coordinated launch event. | Scheduled publishing and release bundling are available, with workflow controls layered through their content management features. |
| Editor customisation | Studio is a React app you ship: Structure Builder for navigation, custom input components for field UI, App SDK + Functions for apps/automation. | Editing UI is a polished but largely fixed SaaS surface; extension happens via apps and the App Framework rather than owning the editor. |