Founder & Lead Engineer, RAITHub
A headless CMS lets your marketing team edit landing pages, banners, navigation and product copy without a developer or a deploy, while the commerce engine stays the source of truth for price, stock and orders. The work is modelling content as structured types, building a draft-preview-publish workflow the team trusts, and testing that a draft never leaks and a publish goes live.
If you would rather have it built for you, see how RAITHub would build this below.
This is the content layer only. The commerce engine behind the storefront, the catalogue, cart, orders and payments, is covered in building a headless ecommerce SaaS, and whether to go headless at all is in when to move from Shopify to headless. Here the decision to split content from commerce is already made.
What belongs in the CMS, and what does not?
Content that marketing owns goes in the CMS; anything that affects money or stock stays in the commerce engine. Price, inventory and the order record are never edited in the CMS, because they must stay correct and transactional. A product's marketing copy, hero image and badges can live in the CMS and be joined to the commerce product by its ID at render time. Getting this boundary wrong is the main way a content project turns into a pricing bug.
| In the CMS | In the commerce engine |
|---|---|
| Landing pages and marketing blocks | Price and currency |
| Banners, promotions copy and badges | Stock and availability |
| Navigation, footer and static pages | The order and payment record |
| Product descriptions, imagery and SEO copy | The canonical product and variant data |
| Blog and help articles | Cart and checkout logic |
How do you model the content?
As structured types with named fields, not one big rich-text blob. A landing page is a sequence of typed blocks (hero, product grid, text, image, FAQ), each with fields the editor fills in. Structured content is what lets the same block render consistently, be reused across pages, and be tested. A product-content type holds only the marketing fields and a reference to the commerce product ID, so the two systems join cleanly without duplicating price or stock.
// A landing page as structured blocks. Each block is a typed union member,
// so the renderer and the tests both know every field that can appear.
type Block =
| { type: 'hero'; heading: string; subheading?: string; image: ImageRef; cta?: Link }
| { type: 'productGrid'; title: string; productIds: string[] } // IDs resolved at render from commerce
| { type: 'richText'; body: PortableText }
| { type: 'faq'; items: { q: string; a: string }[] }
interface LandingPage {
slug: string
status: 'draft' | 'published'
blocks: Block[]
seo: { title: string; description: string }
}
The productGrid block stores only product IDs; the storefront resolves current price and stock from the commerce engine when it renders, so the CMS never holds a stale price. That join is the seam to watch, and the thing to test.
How should draft, preview and publish work?
Three states and one rule: a draft is visible only in preview, and only a published version reaches shoppers. Editors work on a draft, open a preview that renders the draft against the live storefront (so a product grid shows real products and real prices), and publish when they are happy. Publishing swaps the live version; it does not edit the live document in place, so you can roll back.
- Preview runs the real storefront in a draft-aware mode, behind a token or a signed link, never on a public URL. It must resolve commerce data live, or the preview lies about price and availability.
- Publish promotes the draft to a published version and triggers a revalidation of the affected pages, so the change appears without a full deploy.
- Scheduling is a published version with a start time; the storefront shows the previous version until then.
The rendered page must still be fast, so content is cached and revalidated on publish, not fetched fresh on every request. Storefront speed is a Core Web Vitals concern (web.dev: Web Vitals), and a CMS that fetches content on every request undoes it.
Why is preview the part that breaks most?
Because it is the one place draft and live content mix, and the two failure modes are opposite and both serious. A draft that leaks onto a public URL shows shoppers unfinished or wrong content, sometimes a promotion that is not live yet. A publish that does not revalidate leaves the old content up, so the team publishes and nothing changes, then edits again and makes it worse. Both are invisible without a test, because neither throws an error.
How do you test a content workflow?
import { test, expect } from '@playwright/test'
test('a draft is never visible on the public URL', async ({ page }) => {
await createDraft({ slug: 'summer-sale', heading: 'Secret draft' })
await page.goto('/p/summer-sale') // public, no preview token
await expect(page.getByText('Secret draft')).toHaveCount(0) // draft must not leak
})
test('preview shows the draft and resolves live product data', async ({ page }) => {
await page.goto('/p/summer-sale?preview=' + previewToken())
await expect(page.getByText('Secret draft')).toBeVisible()
await expect(page.getByTestId('grid-price-blue-shirt')).toHaveText('$25.00') // live price, not a CMS copy
})
test('publishing makes the change live and revalidates the page', async ({ page }) => {
await publishDraft('summer-sale')
await page.goto('/p/summer-sale') // public again
await expect(page.getByText('Secret draft')).toBeVisible() // now published
})
Also unit-test the renderer against each block type, including an empty page and an unknown block (which should render nothing, not crash), and test that a referenced product that no longer exists is handled gracefully. These are the content equivalents of the money-path tests in the pre-launch QA checklist.
Buy, build or hire?
| Option | Choose this when | The catch |
|---|---|---|
| A hosted headless CMS (Sanity, Contentful, Storyblok and similar) plus glue | You want editing, preview and scheduling out of the box | You still build the storefront integration, the commerce join and the tests that stop a draft leaking |
| The platform's built-in pages | A hosted store whose native page editor is enough | Structured content, reuse and multi-front-end often outgrow it |
| Custom CMS | Your content model, workflow or governance is specific and a hosted CMS does not fit | You own the editing experience, which is a product in itself |
For most stores a hosted headless CMS plus a well-built storefront integration is the right balance: the CMS handles editing and workflow, and the build is the integration and the tests.
How long does it take to build yourself, and what is the risk?
For an experienced developer wiring a hosted headless CMS into an existing storefront, our estimate is 2 to 4 weeks: content types, the block renderer, preview, publish with revalidation, and the tests. A custom CMS is far more, because the editing experience is its own product. The main risk is the commerce join: storing price or stock in the CMS gives stale numbers, so keep money and stock in the commerce engine and resolve them at render. The second risk is a leaking preview; test that a draft is invisible on a public URL before you let the team use it.
Why RAITHub for this
- Content and commerce split in production. TheSkinProof, the founder's own venture built and run by RAITHub, runs editorial content alongside a commerce engine across 5 portals, 217 endpoints and 750+ tests, with the two joined cleanly.
- Organic content that works. Sundor Skin's content site brings in wholesale buyers through organic search with no paid ads; see ecommerce SEO without ad spend.
- Workflows are tested. Draft, preview and publish get Playwright tests gated in CI, so a draft cannot leak and a publish actually goes live.
When you don't need us
- The platform's pages are enough. If your store's built-in editor covers your content, use it.
- A hosted CMS with a template fits. A standard integration your own developer can wire may be all you need.
- You only need the patterns. The content model and tests above are a fair start.
How RAITHub would build this
- Content model: structured types and typed blocks, with product content referencing the commerce product by ID.
- Workflow: draft, preview behind a token with live commerce data, and publish that revalidates affected pages.
- Performance: content cached and revalidated on publish, so the storefront stays fast.
- Tests: renderer tests per block and Playwright tests that keep drafts private and publishes live, gated in CI.
Timeline: integrating a hosted CMS into an existing storefront is typically within the 4 to 6 week fixed-scope range; a larger headless build sits in the 6 to 12 week backend range. See SaaS development, API and backend development and the ecommerce industry page.
You receive: automated tests and CI covering the content workflow, handover docs, and full IP in your name under NDA.
Next step: book the free 15-minute technical audit with the content your team edits and the CMS you are considering, and we will follow up with a written fixed quote.
Frequently asked questions
What is a headless CMS for an ecommerce store?
A content system that manages marketing pages, banners and copy separately from the storefront that displays them, delivering content over an API. It lets marketing edit without a deploy, while the commerce engine stays the source of truth for price, stock and orders.
What should live in the CMS and what should not?
Content marketing owns, landing pages, banners, navigation and product copy, lives in the CMS. Price, stock and the order record stay in the commerce engine, because they must be correct and transactional. Join them by product ID at render time.
How does draft, preview and publish work?
Editors work on a draft that is visible only in preview behind a token, preview against the live storefront so product data is real, then publish to promote the draft and revalidate the affected pages. Scheduling is a published version with a start time.
Why do drafts sometimes leak to shoppers?
Because preview and public rendering share code and the draft check is missed on a path. Test explicitly that a draft is invisible on a public URL and visible only with a preview token, before letting the team rely on it.
Should I store product prices in the CMS?
No. Store only marketing content and a reference to the commerce product ID, and resolve price and stock from the commerce engine when the page renders. A price copied into the CMS goes stale the moment it changes in commerce.
Should I buy a hosted CMS or build one?
For most stores, buy a hosted headless CMS and build the storefront integration and tests. Build a custom CMS only when your content model, workflow or governance is specific enough that a hosted product genuinely does not fit.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.