Sanity for Headless Commerce: The Shop Sells, the CMS Tells the Story

Shop systems sell well and tell stories badly: guides, brand story and campaign pages need a home of their own. Sanity provides it as a content layer next to Shopify or Medusa: content references products instead of copying them, GROQ queries both together, Next.js renders the page. When the setup pays off and when it is oversized: that is what you will read here.
10 min readMatthias RadscheitMatthias Radscheit
Happycodingen-US

TL;DR

Shop systems sell well and tell stories badly: guides, brand story and campaign pages need a home of their own. Sanity provides it as a content layer next to Shopify or Medusa: content references products instead of copying them, GROQ queries both together, Next.js renders the page. When the setup pays off and when it is oversized: that is what you will read here.

  • The shop stays the source of truth for prices, variants and inventory; Sanity holds the editorial content. Content references product IDs instead of duplicating product data.
  • Sanity Connect for Shopify syncs products, variants and collections as documents into the Content Lake: updates typically within a few seconds, daily drift correction, metafield import since July 2026 (as of October 2026).
  • For Medusa, Medusa itself documents the sync path: a dedicated Sanity module, subscribers on product.created and product.updated, the product ID as the document ID.
  • GROQ combines content and product context in one query: the -> operator resolves references, references() finds every guide that mentions a product, and joins work without reference fields if needed.
  • The setup is oversized for a pure catalog without content ambition: without editorial capacity the second system stays empty, and an empty CMS is the most expensive variant.

The shop sells. Who tells the story?

I know this pattern from many first calls: the shop runs, the catalog is well maintained, the checkout converts. Then marketing wants a campaign landing page, a buying guide, a brand story. And suddenly it shows: the shop system has no home for any of it.

A quick note on who is writing here: I am Matthias, a one-person agency called happycoding.agency in Berlin. I build commerce projects with Medusa and Shopify and put Sanity next to them as the content layer; this website runs on exactly that stack of Sanity and Next.js.

In this article I show you the division of labor that has proven itself in my projects: the shop sells, Sanity tells the story, the frontend brings both together. You will read how content points to products instead of copying them, what GROQ contributes, and when you are better off skipping the whole setup.

Why your shop's built-in tools give up on content

First, to be fair to Shopify: pages and a blog are on board, and for an about page they are enough. But the content is glued to the theme. A guide that presents three products ends up there as running text with pasted-in product images and hand-set links. If a price or a product title changes, nobody notices.

Shopify is not entirely unstructured, either: metaobjects define custom content types with fields, even with product references. But they remain records in the admin, rendered through the theme. They are fine for an FAQ module or a dealer list; an editorial piece with its own dramaturgy, freely combinable modules and a preview is not something you can model with them.

I see the result regularly in audits: campaign pages built as one-off page-builder pieces outside the shop, the blog living on a WordPress subdomain, and nobody knows anymore where which content is maintained. Three systems, three logins, no connection to the products.

Medusa takes this even further: it deliberately ships no CMS at all. The framework sees itself as a commerce backend and leaves the content question to you; what Medusa fundamentally is, you can read in the Medusa basics article. For projects like the ones I describe on my MedusaJS service page, a content system next to the shop is not a nice-to-have but part of the architecture.

The short version: a shop system is a cash register with a catalog, not an editorial system. If you try to press both out of one tool, you get the weaker half of each.

The division of labor: the shop calculates, Sanity tells the story, Next.js renders

The architecture has three clearly separated responsibilities. The shop remains the source of truth for everything that sells: products, variants, prices, inventory, checkout. Sanity holds everything that tells a story: guides, brand pages, campaign landing pages, SEO content. The Next.js frontend queries both systems and assembles them per page.

ResponsibilitySystem
Products, variants, prices, inventory, checkoutShop (Medusa or Shopify)
Guides, brand story, landing pages, FAQSanity
Assembly and deliveryNext.js frontend

Sanity does not store content as pages but as structured documents in the Content Lake: typed fields, queryable through an API. The core point for this article: content is data, not HTML pages.

The frontend does the assembling: at render time, Next.js fetches the content from Sanity via GROQ and the product data from the shop API. Static content can be cached and selectively rebuilt on changes; volatile values like price and stock are fetched at runtime. The guide stays fast and the price stays current.

For your editors this means: they work in Sanity Studio, a customizable interface that I tailor to the content types in each project. Product data shows up there as linkable documents, not as an editing form: the price is still changed in the shop. This boundary enforces discipline, because it makes visible right in the editor which system owns which truth.

This website serves as evidence: every blog post on happycoding.agency is a Sanity document with fields for teaser, key takeaways, FAQ and sources, and the frontend decides how that becomes a page. The same principle carries in commerce, except that there a shop system sits at the table as well.

References instead of copies: content points to products

Now for the core of the pattern. The most common wrong turn in content commerce projects is copying product data into the CMS: title, price and image get entered by hand and are wrong three weeks later. The right answer is a reference: the content document stores a pointer to the product, never its data.

Reference means, concretely: the content document stores the ID of the product document or the product ID from the shop. Nothing more. Everything else, from the title to the stock level, stays where it is maintained.

Shopify: Sanity Connect syncs the catalog

For Shopify, Sanity ships the tool itself: Sanity Connect, the official app from the Shopify App Store. It syncs products, variants and collections as documents into your Content Lake; changes from Shopify typically land there within a few seconds (as of October 2026). A daily drift correction reconciles deviations automatically and catches missed webhooks.

Since July 2026, Sanity Connect can also import Shopify metafields on request, as a read-only array on the product documents. Two things you should know: synced documents count toward your Sanity document quota, one document per product, variant and collection. A custom sync handler lets you shrink that if needed, for example by storing variants as objects on the product document instead of as separate documents.

Medusa: sync via workflow

For Medusa there is no ready-made app, but there is an officially documented path: Medusa's integration guide describes a Sanity module in the Medusa backend, subscribers on the product.created and product.updated events, and a workflow that mirrors products as documents into Sanity, with the product ID as the document ID (as of October 2026).

Your guide document then references these product documents, and the frontend fetches price and availability fresh from the Medusa API at runtime. The shop keeps the volatile truth; Sanity keeps the story.

GROQ connects the two

The query language GROQ resolves references directly in the query. The arrow operator -> follows a reference and returns the target document: *[_type == "guide"]{ title, products[]->{ store { title, slug } } } fetches a guide together with its linked product documents in a single call.

GROQ handles the reverse direction too: references() finds every content document that points to a given product. That answers the question "Which guides mention this product?" in one line and powers the "Related guides" block on the product page. And because GROQ allows joins on arbitrary conditions, the pattern even works without reference fields if it has to, for example via a product ID stored as a string.

The same idea keeps things current in the other direction: a webhook from Sanity triggers a targeted rebuild of the affected pages on publish. If your editors publish a guide at 14:00, it is live at 14:01, without a deployment and without anyone having to ask a developer.

Portable Text: running text with products built in

That leaves the question of how a product reference ends up in the middle of the guide text. Sanity's answer is Portable Text: running text is not stored as HTML but as a JSON array of typed blocks. Paragraphs, headings and lists are standard blocks; custom block types are yours to define.

That is exactly where the lever for commerce sits: you define a block type called "product teaser" that contains nothing except a reference to a product document. Your editors drag it between two paragraphs, and your Next.js renderer decides how it becomes a buy tile with the current price.

The second win is reusability: because the text is structured data, the web frontend renders it as a page, the app as a native view and the newsletter as email markup, all from the same source. Portable Text is not a Sanity secret either, but an openly documented specification with libraries for React and other environments.

The takeaway: a guide in Portable Text is not a lump of HTML but a list of building blocks. Products are one of them.

The business case: guide traffic with a path to purchase

Why go to this effort? Because product pages alone rarely win in search. Transactional keywords are crowded with marketplaces and price comparison sites; informational questions like "how to adjust grind size" or "which rain jacket for hiking" face thinner competition. Those are exactly the queries a guide captures, and the product teaser in the text creates the path to purchase.

A scenario instead of an invented case study: a D2C retailer for espresso equipment writes twelve guides on preparation and care. Each one references two to three products. When an article climbs in the rankings, the product pages benefit through the internal links, without anyone updating prices: the frontend pulls those from the shop.

Then there is the maintenance math: twelve guides with copied product data mean twelve manual edits and twelve sources of error at every price change. With references, the same price change costs exactly zero content work. The bigger your catalog and the more often your prices change, the heavier this argument weighs.

The second use case next to search is campaigns: for paid traffic, marketing needs landing pages on a weekly cadence, not on a release cycle. With a page builder made of predefined sections, your editors assemble them on their own, product teasers via reference included. The developer defines the building blocks exactly once; after that, pages come together without them.

That the mechanics hold is something I can see in my own setup: the blog on this website brings in the majority of organic visitors, and the articles link in a structured way to my service pages. The principle is identical, except that on your end a shopping cart stands at the finish line instead of a consulting call.

When the setup is oversized

To be blunt: there are shops I advise against this architecture. If your catalog is the product and content ambitions are absent, a second system buys you nothing except operational overhead. An about page and three legal texts do not justify a dedicated content backend.

Three questions from my project conversations:

  • Editorial capacity: Do you have someone who produces content regularly, in-house or external?
  • Volume: Do you need more than a handful of editorial pages per year?
  • Interlocking: Should content reference products instead of just sitting next to them?

Two times no means: stay with your shop's built-in tools, and come back when that changes. The migration path is not going anywhere; Sanity can be placed next to a running shop later on.

There is a middle stage, by the way: if all you are missing at first is the guide section, start with two or three document types and leave out the page builder. Nobody forces you to build the complete content model on day one; it can grow with your ambition.

And do the math soberly: Sanity itself starts with a free plan (as of October 2026); the real costs sit in modeling, sync and maintenance. An empty CMS is the most expensive variant: you pay for the architecture and harvest no traffic.

Next steps

If your shop sells but tells no story: go through the three questions from the last section. If the answer is yes twice, the architecture from this article is worth a closer look. How I set up Sanity projects, from content modeling to go-live, is on my Sanity service page.

Want to know whether the pattern fits your shop? Send me your stack and your content plans, and in 30 minutes I will tell you honestly whether the content layer pays off or whether your shop's built-in tools are enough: Book a free initial consultation.

Frequently asked questions

My shop already has a blog. Why would I need Sanity?
For occasional posts, the shop blog is enough. Sanity pays off once content needs structure: reusable modules, FAQ fields, campaign landing pages and above all references to products as data objects. A shop blog stores text; Sanity stores content as queryable data that your frontend assembles any way you need.
How do my Shopify products get into Sanity?
Through Sanity Connect, the official app from the Shopify App Store. It syncs products, variants and collections as documents into the Content Lake, typically within a few seconds of saving. A daily drift correction repairs deviations automatically, and since July 2026 Shopify metafields can be imported as well (as of October 2026).
Does the pattern work with Medusa too?
Yes. Medusa documents the integration officially: a Sanity module in the Medusa backend, subscribers on product.created and product.updated, and a workflow that mirrors products as documents into Sanity, with the product ID as the document ID. Your content references these documents; the frontend fetches price and availability from the Medusa API at runtime.
Does my content go stale when prices or product data change?
No, and that is exactly the point of the reference pattern: your content stores only the pointer to the product, never price or title as a copy. The frontend pulls the current values from the shop or from the synced product documents at render time. A price change means zero manual work in the CMS.
Do synced Shopify products count toward my Sanity quota?
Yes. Sanity Connect creates one document per product, variant and collection, and these documents count toward your document quota. For large catalogs a custom sync handler is worth a look: with it you sync only products, for example, and store variants as objects on the product document instead of as separate documents.
When is Sanity next to the shop unnecessary?
When your catalog is the product and nobody produces content regularly. For an about page and legal texts, your shop's built-in tools are enough. Sanity pays off only with real content ambition: guides, campaign pages, brand stories. The entry point is not going anywhere; the system can be placed next to a running shop later on.

Sources

Related articles

Open for select projects

Let's talk about your project

Book a no-obligation call, send us an email, or use the form – we'd love to hear from you.

150+
Completed projects
15
Years of experience
8
Senior‑level team members