~/oybek.dev
Book a call
insights
CMSMarch 4, 2026· 9 min read

Sanity in the real world: building CMS setups marketing teams keep using

The gap between a CMS demo and a CMS still in daily use a year later comes down to four decisions: a page-builder of reusable sections, real roles, live preview, and field-level localisation. Here's what worked across NIO, Trivandi and The Savvy Way.

Most CMS projects look great in the demo and get abandoned by month three. The marketing team goes back to emailing the developer screenshots of text they want changed, and the expensive headless setup becomes a database nobody touches. I've shipped Sanity CMS in production across roughly a dozen live builds — NIO, Geely UAE, Trivandi, The Savvy Way, Siella Beauty — and the ones still in daily use a year later share the same four decisions. None of them are about Sanity features. They're about whether you modelled the content for the people who actually edit it.

That is the real line between a CMS demo and a CMS in production: not the stack, the modelling.

Build a page-builder of sections, not a wall of fields

The first mistake I see in inherited Sanity projects is a content model that mirrors a single Figma page. One homePage document with forty named fields — heroTitle, heroSubtitle, featureOneIcon, featureTwoIcon, testimonialQuote. It demos perfectly because it matches the mockup exactly. Then marketing wants to move the testimonials above the features, or add a second testimonial, or reuse that pricing block on a landing page, and they can't. The model is a screenshot, not a system.

What survives is a page-builder: a pageBuilder array where the editor inserts and reorders typed sections — hero, split prose, card grid, media, pricing, CTA. On this site that's 21 section types behind one switch (block._type). The same hero block renders on a case study, a service page, and a landing page. The editor drags sections into the order they want and the frontend renders whatever they assembled. Adding a section is two schema edits and a React component; the marketing team never files a ticket to reorder a page again.

Model the content, not the page. A field that only makes sense in one layout is a field you'll be asked to move in three months.

The discipline that makes this work is reuse. Categories, tags, authors, contact details — these are reference documents, not free text retyped on every page. On this build, contact and social details live in one siteSettings singleton referenced by the footer, the contact form, and the JSON-LD. Change a phone number once, it updates everywhere. The moment you let editors type the same value in two places, they will drift, and someone will email you about the wrong number on the careers page.

Sanity Studio page-builder showing a stack of reorderable section blocks in the document editor
The page-builder: editors assemble pages from typed sections instead of filling a fixed form.

Set up roles and workflows before the model rots

A content model can be perfect and still fail because everyone has admin. When every editor can edit every field, schemas drift, required content gets deleted, and drafts get published before they're reviewed. Production Sanity needs three things most demos skip.

  • Real roles. Sanity's role system lets you give marketing edit access to content documents while locking down schema-adjacent singletons like site settings, navigation, and footer. Editors shouldn't be able to break the structure they depend on.
  • Draft and publish as a real boundary. Sanity's draft/published split is built in — nothing an editor types is live until they publish. For teams that coordinate launches, Content Releases let you stage a batch of changes and publish them together at a set time, which matters when a campaign page, a pricing change, and a banner all have to go live at once.
  • A desk structure built for the team, not the schema. Out of the box, Sanity shows a flat list of every document type. On larger sites I build a custom structure — on this one, pages are organised into a folder tree by splitting the slug on /, so editors navigate by URL path the way they think about the site, not by document type.

The test is simple: can a new marketer publish a correct page on their first day without asking a developer which field does what? If the answer is no, the model is too clever.

Why live preview is the feature that earns adoption

This is the single thing that decides whether a marketing team keeps using the CMS. If editing means typing into a form, hitting save, switching tabs, and refreshing the live site to see what happened, people stop. The feedback loop is too slow and too uncertain.

Sanity's Presentation tool and the Live Content API close that loop. The editor sees the real rendered page next to the form, clicks any piece of text on the page, and the corresponding field opens. Changes stream in live — no save-and-refresh dance. On the Next.js side this is the <SanityLive> component subscribing to content updates and re-rendering on mutation. The editor stops thinking about "fields" and starts editing the page directly, which is the entire point.

There is one trap worth knowing if you build this yourself. Visual editing injects invisible metadata characters (stega) into every string so the click-to-edit mapping works. Display text is fine left as-is, but any string you use for logic — a className, an href, an enum comparison, a React key — has to be cleaned first or it silently breaks. Every comparison and link on this site runs through a clean() helper for exactly this reason. Get it wrong and you'll spend an afternoon wondering why a conditional never matches.

Split-screen of the Sanity Presentation tool with the live website on the right and the editable field highlighted on the left
Click any element on the live page to jump straight to the field that controls it.

Use field-level localisation that doesn't break

Half my clients serve more than one language — Trivandi runs in several, and most of the UAE work needs English plus Arabic or Russian. The wrong way to do this is duplicating the whole document per language. Now you have two documents that drift, two slugs to keep in sync, and an editor who updates the English page and forgets the other one.

The model that holds up is field-level localisation: one document, and each translatable field is an array of language-tagged values. The frontend resolves the requested locale with a hard-coded English fallback, so a page that only exists in English still renders correctly in every locale — it never 404s because a translation is missing. A half-translated page degrades gracefully instead of breaking.

One detail bites everyone scripting against this: in the internationalised-array format, the locale lives in a language field on each array item, not in the array key. The key is just a unique identifier. I've watched migrations write the locale into the key and quietly corrupt every localised field in the dataset. If you script bulk edits, target language, never the key.

Localisation also pairs with AI now. I have Sanity AI Assist configured with a brand-voice translation styleguide and a do-not-translate list, so product and brand names — Next.js, Sanity, NIO — stay in Latin script while the prose gets a first-pass translation a human then reviews. It doesn't replace a translator. It removes the blank-page step.

A single Sanity document with one translatable field expanded to show English and Arabic values side by side
Field-level localisation keeps both languages in one document instead of two that drift apart.

What a production Sanity build looks like a year later

The builds still in production share a profile. The marketing team publishes campaigns themselves without a developer in the loop — that was the explicit goal on NIO, and the architecture became the base for the Geely UAE storefront. New pages get assembled from existing sections in an afternoon. Translations ship gracefully even when they're incomplete. And the schema hasn't rotted, because editors never had permission to break the parts that hold it together.

None of that comes from Sanity being a good CMS, though it is. It comes from modelling content for the people who edit it every day instead of for the mockup you're shipping this week. If you're choosing a headless CMS for a marketing team, that's the only question that matters — and the same discipline holds whether you build on Sanity alone or pair it with MedusaJS for commerce.

If you want a CMS your team actually runs, that's the kind of build I do as a fractional CTO. For the commerce angle, I've written up Hydrogen vs MedusaJS.

FAQ

Is Sanity good for non-technical marketing teams?

Yes, but only if it's modelled for them. Sanity gives you the tools — a flexible content model, roles, and a live Presentation editor — but a CMS handed over as a wall of forty fields will be abandoned regardless of the platform. The adoption comes from a page-builder of reusable sections plus live click-to-edit preview, not from the CMS choice alone.

What is the difference between field-level and document-level localisation in Sanity?

Document-level localisation duplicates the whole document per language, which means two documents that drift apart and slugs to keep in sync. Field-level localisation keeps one document where each translatable field holds language-tagged values. Field-level is what survives in production because a missing translation falls back to English gracefully instead of leaving an orphaned, out-of-date duplicate.

How does live preview work in Sanity in production?

Sanity's Presentation tool renders your real website next to the editor, and the Live Content API streams changes in without a manual refresh. In a Next.js app a `<SanityLive>` component subscribes to content updates and re-renders on every mutation. Editors click an element on the live page to open the exact field that controls it.

Do I need a page-builder, or can I model each page directly?

For a one-off page that will never change, direct modelling is fine. For a marketing site that grows and gets reorganised, a page-builder of typed, reorderable sections is what keeps the team self-sufficient. The rule: if a field only makes sense in one specific layout, it will eventually need to move, and you'll be the one moving it.

What is the biggest mistake when scripting bulk edits on localised Sanity content?

Writing the locale into the array key instead of the dedicated `language` field. In the internationalised-array format the key is just a unique identifier and the locale lives in `language`. Targeting the key in a migration silently corrupts every localised field, and it's the single most common scripting mistake I see.

Is Sanity still a good choice in 2026?

Yes. As of mid-2026 the Live Content API, the Presentation visual-editing tool, and Content Releases are all stable and actively maintained, and they pair cleanly with Next.js 16. The platform isn't the differentiator, though — the content model is. A well-modelled Sanity build outlives a badly-modelled one on any CMS.

Have something like this in mind?

Start a project