Skip to main content

The Clione render contract for commercetools

Who this is for: the developers who build the storefront. Not the merchant. If you were sent here, somebody connected a commercetools project to Clione and the work below is the half that makes it visible.

This page is public because the people who have to act on it do not have accounts in our internal docs, and a contract only one side can read is not a contract.

Why there is anything for you to do​

On Shopify, BigCommerce and WooCommerce, Clione publishes into the storefront itself — a theme block, a widget, a wp_head hook. commercetools has no storefront. Clione writes four attributes onto your product projections, and your front end renders them.

If it does not, nothing changes in your HTML. A perfect write and a front end that never reads the attributes produce exactly the same page as no Clione at all — because the consumers here are crawlers and AI assistants, and they do not execute JavaScript and do not call our API.

The four attributes​

Declared on your product type, read off the variant (commercetools has no product-level attribute). Every one is ltext — a LocalizedString — because a product projection is already per locale:

AttributeTypeCarries
clione_meta_titleltextthe meta title, per locale
clione_meta_descriptionltextthe meta description, per locale
clione_faqltexta JSON array of { question, answer }, per locale
clione_jsonldltextthe schema.org @graph, as JSON, per locale

Treat these names as frozen once you have shipped against them. Renaming one is a deploy on both sides in the same window: in between, every product either carries the old name your code no longer reads or the new name your deployed code does not know. If you need different names, say so before you go live.

What you have to render​

Three things, in the first HTML response, with no JavaScript:

  1. The title and description from clione_meta_title and clione_meta_description.
  2. One <script type="application/ld+json"> holding clione_jsonld verbatim.
  3. The FAQ as real markup — <details>/<summary> works and needs no JS.

And two documents on your domain, not ours:

  • /llms.txt — an index of your pages
  • /.well-known/agents.json — the same, for agents

They have to be on your origin. A document a crawler finds on another domain describing your site is discounted, so the helpers below throw if you hand them anything but your own origin.

What you do NOT have to do​

Compose any schema.org. clione_jsonld is already the graph Clione built for that entity — Organization, WebSite, BreadcrumbList, the Product and the FAQPage, cross-referenced by @id. Emit it. Building your own means two sources of truth for one page, and ours is the one the merchant reviews and approves.

Copy this​

The reference implementation is a Next.js App Router app in the Clione repository:

packages/seo-client/examples/commercetools-next/
lib/clione-contract.ts <- copy this file; it has no dependencies
app/products/[key]/page.tsx <- a SERVER component
app/llms.txt/route.ts
app/.well-known/agents.json/route.ts
mock/products.ts <- one enriched product, one never enriched

lib/clione-contract.ts imports nothing — not from Clione, not from a framework — so it is yours the moment you copy it. Change the markup, the class names, the fallback rules. Clione keeps an identical copy on its side, and a test runs both over the same fixtures on every build, so the file cannot drift away from what the write side produces.

Ask your Clione contact for the directory if you do not have the repository.

Four things that are easy to get wrong​

A client component. The App Router default is a server component; keep it. ssr: false, a useEffect fetch or a client-side mount all produce an empty shell for the only readers that matter.

React escaping the JSON-LD. Putting the graph in a <script> from JSX needs dangerouslySetInnerHTML; without it React escapes the body into something no parser reads as JSON-LD. Escape < to < first — a </script> inside any string ends the element as far as the HTML parser is concerned, whatever the JSON says. The reference does both.

A silent locale fallback. Serving English meta on a German page looks correct to everyone except the reader. The reference ships with no fallback: a locale Clione has not written yet renders your own native meta instead. Your call to change, but make it deliberately.

Rendering nothing when a signal is missing, and saying nothing. Every read in the contract answers a reason — not_enriched, locale_missing, invalid_json, wrong_shape — so you can log it. A renderer that cannot tell "nobody has enriched this product yet" from "the attribute holds something we could not parse" is how a bad write survives a month.

Check it the way we do​

No browser, no JS execution — the same commands we run against every other platform:

UA="Mozilla/5.0 (compatible; Googlebot/2.1)"
curl -sSL -A "$UA" https://your-shop.example.com/products/some-key -o /tmp/p.html
grep -c '"@type":"Product"' /tmp/p.html # >= 1
grep -c '"@type":"FAQPage"' /tmp/p.html # >= 1
grep -c '"@type":"Organization"' /tmp/p.html # >= 1
grep -c '"@type":"WebSite"' /tmp/p.html # >= 1
grep -c '"@type":"BreadcrumbList"' /tmp/p.html # >= 1
grep -c '<summary>' /tmp/p.html # >= 1 the visible FAQ

curl -sS https://your-shop.example.com/llms.txt | grep -c 'clione.ai' # == 0

# The control that makes the rest mean something: a product with no Clione
# attributes must render NONE of it.
curl -sSL -A "$UA" https://your-shop.example.com/products/not-enriched-yet \
| grep -c 'application/ld+json' # == 0

That last command is the one people skip. Without it, every check above is also satisfied by a page that prints a hardcoded graph, and a product that lost its signals would look fine.

Status​

The write side — Clione setting these attributes on your products — is in development. Reading your catalog and enriching it work today. Nothing appears on your site until both this contract and the write side are live.