Skip to main content

@clione/seo SDK

@clione/seo is a framework-agnostic npm package for fetching Clione's enriched SEO/AEO/GEO signals in headless storefronts. Zero dependencies, two modes (API or metafield), with framework adapters for Next.js, Hydrogen, and Nuxt / Vue Storefront.

If you're shipping a custom-built frontend (Catalyst, Hydrogen, Vue Storefront, Next.js Commerce, your own thing), this SDK is the recommended way to consume Clione signals.

For non-headless stores (BC Stencil, Shopify Liquid themes), prefer the Schema Injector (BC) or the theme app block (Shopify) — they handle the same delivery server-side without your frontend doing anything.


Install

npm install @clione/seo
# or
pnpm add @clione/seo
# or
yarn add @clione/seo

The package is published as @clione/seo. It has no runtime dependencies (uses the platform fetch).


Two modes

ModeWhen to use
API mode (getSignals)You want fresh signals at request time. Calls the SEO Signals API.
Metafield mode (extractSignals)You're already fetching the product from Shopify GraphQL and the clione.jsonld metafield is in the response. Extract signals from there, no extra request.

API mode is the default and works on both platforms. Metafield mode is Shopify-only today (BC headless metafield mode is planned).


API mode

import { getSignals } from '@clione/seo'

const { signals, entity } = await getSignals('product', productId, {
apiKey: process.env.CLIONE_API_KEY,
storeId: process.env.CLIONE_STORE_ID,
platform: 'shopify', // or 'bigcommerce'
baseUrl: 'https://api.clione.ai', // optional — defaults to api.clione.ai
})

Returns the same shape as GET /api/v1/public/seo-signals/... documented in SEO Signals API.

Entity types

type EntityType = 'product' | 'category' | 'collection' | 'page'

Use category on BigCommerce, collection on Shopify. Mixed-platform code should branch on platform to pick the right entity type.


Metafield mode (Shopify only)

If you're already fetching the product from Shopify's Storefront API with metafields, extract the JSON-LD directly:

import { extractSignals } from '@clione/seo'

const product = await shopifyClient.query(/* GraphQL with clione.jsonld metafield */)
const { signals } = extractSignals(product)

This avoids a second network call to Clione. Useful when:

  • You already pay the cost of fetching the product from Shopify.
  • You want signals to be consistent with the product version you just rendered.
  • You're on the edge and want fewer fan-out calls.

The metafield must be readable in your Storefront API access token's allowed scopes.


Framework adapters

Next.js (App Router)

// app/products/[handle]/page.tsx
import { getSignals } from '@clione/seo'

export async function generateMetadata({ params }) {
const { signals } = await getSignals('product', params.handle, {
apiKey: process.env.CLIONE_API_KEY,
storeId: process.env.CLIONE_STORE_ID,
platform: 'shopify',
})

return {
title: signals.metaTitle,
description: signals.metaDescription,
alternates: { canonical: signals.canonicalUrl },
openGraph: signals.ogTags,
}
}

export default async function ProductPage({ params }) {
const { signals } = await getSignals('product', params.handle, /* ... */)
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(signals.jsonLd) }} />
{/* rest of page */}
</>
)
}

There's also @clione/seo/react with hooks (useSignals) for client-side fetches.

Hydrogen (Shopify)

// app/routes/products.$handle.tsx
import { useLoaderData } from '@remix-run/react'
import { extractSignals } from '@clione/seo'

export async function loader({ params, context }) {
const product = await context.storefront.query(PRODUCT_QUERY_WITH_METAFIELD, {
variables: { handle: params.handle },
})
const { signals } = extractSignals(product)
return { product, signals }
}

export function meta({ data }) {
return [
{ title: data.signals.metaTitle },
{ name: 'description', content: data.signals.metaDescription },
]
}

Nuxt / Vue Storefront / Alokai

// pages/products/[handle].vue
<script setup>
import { useClioneSignals } from '@clione/seo/nuxt'

const route = useRoute()
const { signals } = await useClioneSignals('product', route.params.handle, {
platform: 'shopify',
})

useHead({
title: signals.metaTitle,
meta: [{ name: 'description', content: signals.metaDescription }],
script: [{ type: 'application/ld+json', innerHTML: JSON.stringify(signals.jsonLd) }],
})
</script>

Configuration

The getSignals call accepts:

OptionDefaultNotes
apiKey(env: CLIONE_API_KEY)Required.
storeId(env: CLIONE_STORE_ID)Optional. Disambiguates when an org has multiple stores on the same platform.
platforminferredOptional. Inferred from storeId if provided.
baseUrlhttps://api.clione.aiOverride for self-hosted Clione.
fetchglobal fetchOverride for custom fetch (rare).
cachedefaultPassed through to fetch. Set to 'no-store' to bypass edge cache.

Caching

  • getSignals honors the API's Cache-Control: public, max-age=60 response header. On the edge (Cloudflare Workers, Vercel Edge Functions), this caches for 60 seconds automatically.
  • For longer cache TTLs, wrap the call yourself with your framework's cache primitive (Next.js unstable_cache, Hydrogen withCache, etc.).
  • Clione invalidates server-side caches on every enrichment, so you can cache aggressively without worrying about staleness.

TypeScript

The package ships full TS types:

import type { Signals, Entity, EntityType, Platform } from '@clione/seo'

Per-platform notes

BigCommerceShopify
API mode (getSignals)Yes (product, category, page)Yes (product, collection, page)
Metafield mode (extractSignals)PlannedYes (clione.jsonld metafield)
JSON-LD for productsYesYes (full schema)
JSON-LD for categories/collectionsNot yetNot yet
OG tagsYesYes

Troubleshooting

Error: Clione SEO Signals: 401 — Bad or missing API key. Check the CLIONE_API_KEY env var.

Error: Clione SEO Signals: 403 — API key valid but the Origin header doesn't match the allowed domains list. Update the whitelist in Store → Settings → API Keys.

Error: Clione SEO Signals: 404 — Entity not synced or not enriched, OR wrong platform / storeId. Check the entity exists in your Clione dashboard.

Metafield mode returns empty signals — The clione.jsonld metafield is empty (product not enriched), or your Storefront API token can't read the clione namespace. Add clione to the metafield namespaces in your Shopify app config.

Cold start latency — First call to api.clione.ai from a cold region can be 200–400ms. Cache aggressively to amortize.