SDK @clione/seo
@clione/seo es un paquete npm agnóstico de framework para pintar las señales SEO/AEO/GEO enriquecidas de Clione en storefronts headless. Dos modos (API o metafield), con adaptadores para React / Next.js, Remix / Shopify Hydrogen y Nuxt / Vue Storefront.
Si estás lanzando un frontend a medida (Catalyst, Hydrogen, Vue Storefront, Next.js Commerce, lo tuyo propio), este SDK es la forma recomendada de consumir las señales de Clione.
Para tiendas no headless (BC Stencil, themes Liquid de Shopify) no necesitas el SDK: al publicar desde Clione, el JSON-LD llega server-side, mediante un widget en BigCommerce y los app embeds del theme en Shopify. Mira Propagación de señales.
Instalar
npm install @clione/seo
# or
pnpm add @clione/seo
# or
yarn add @clione/seo
El cliente base usa el fetch nativo del runtime (Node 18+, Deno, Bun, edge runtimes). Los adaptadores necesitan su framework como peer dependency: react ≥ 18 para @clione/seo/react y @clione/seo/remix, vue ≥ 3 para @clione/seo/nuxt.
| Ruta de import | Qué te da |
|---|---|
@clione/seo | El cliente base: createClioneSeo() |
@clione/seo/react | getClioneSeoMetadata, fetchClioneSeo, getJsonLdScriptProps |
@clione/seo/remix | loadClioneSignals, clioneMeta, <ClioneJsonLd>, <ClioneHead>, <ClioneFaq>, CLIONE_STOREFRONT_FIELDS |
@clione/seo/nuxt | useClioneSchemaGraph, useClioneSeo |
Dos modos
| Modo | Cuándo usarlo |
|---|---|
Modo API (getSignals) | Quieres las señales en el momento de la petición. Llama a la SEO Signals API con una API key de Clione. |
Modo metafield (extractSignals) | Ya traes la entidad de la Storefront API de Shopify con el metafield clione.jsonld en la respuesta. Sin petición extra. |
Crear un cliente
import { createClioneSeo } from '@clione/seo'
const clione = createClioneSeo({
source: 'api',
apiUrl: process.env.CLIONE_API_URL!,
apiKey: process.env.CLIONE_API_KEY!, // a `server` key — keep it on the server
platform: 'shopify',
storefrontHost: 'shop.example.com',
})
Configuración
| Opción | Por defecto | Notas |
|---|---|---|
source | 'api' | 'api' o 'metafield'. |
apiUrl | — | URL base de la API de Clione, p. ej. https://api.clione.ai. Obligatoria en modo API. Se respeta una ruta base (https://example.com/proxy/clione funciona). |
apiKey | — | API key de Clione con products:read. Se envía como Authorization: Bearer (getSignals) o X-API-Key (getSchemaGraph). |
platform | 'bigcommerce' | 'shopify' o 'bigcommerce'. Pásala siempre: una tienda Shopify consultada sin ella se busca como BigCommerce. |
storefrontHost | — | El host del storefront para el que renderiza este cliente. Se envía como X-Clione-Storefront-Host; hace falta cuando una key server tiene dominios permitidos. Mira Autenticación. |
Métodos
| Método | Devuelve | Notas |
|---|---|---|
getSignals(entityType, entityId) | Promise<ClioneSeoSignals> | Modo API. Llama a GET /api/v1/public/seo-signals/:platform/:entityType/:entityId. |
getSchemaGraph(entityType?, entityId?, pageUrl?) | Promise<ClioneSeoSchemaGraph | null> | Llama a GET /api/v1/public/schema.jsonld. Sin entidad, devuelve Organization + WebSite para páginas que no son de entidad. |
renderSchemaGraph(entityType?, entityId?, pageUrl?) | Promise<string> | El grafo como string <script type="application/ld+json">, o ''. |
extractSignals(entity) | ClioneSeoSignals | Modo metafield. Sin llamada de red. |
toHeadTags(signals) | SeoHeadTags | Descriptores { title, meta, script } agnósticos de framework. |
renderHead(signals) | string | <title>, <meta name="description"> y el <script> JSON-LD como string HTML. |
Errores. getSignals no lanza excepciones ante un error de red o una respuesta que no sea 2xx: registra un aviso (con el estado HTTP si la respuesta no es 2xx) y devuelve señales vacías (jsonLd: null, metaTitle: null, metaDescription: null, keywords: [], faq: null), así que una caída de Clione nunca rompe el render de tu página. getSchemaGraph devuelve null en los mismos casos. Los dos solo lanzan ante un error de configuración: falta apiUrl, o se llama a getSignals en un cliente creado con source: 'metafield'.
Todo el JSON-LD que pinta el SDK se serializa con < escapado, así que un </script> dentro de una respuesta no puede cerrar la etiqueta. Los heads que generan renderHead, toHeadTags, clioneMeta y <ClioneHead> llevan además <meta name="clione:enriched" content="true">, que la extensión Clione Pulse usa para reconocer un storefront headless.
Modo API
const signals = await clione.getSignals('product', productId)
const headHtml = clione.renderHead(signals)
Tipos de entidad
type ClioneEntityType = 'product' | 'category' | 'collection' | 'page'
Usa category en BigCommerce y collection en Shopify. entityId es el id de Clione de la entidad o, en productos, el id numérico de la plataforma; en Shopify también vale un handle.
Señales
interface ClioneSeoSignals {
jsonLd: Record<string, unknown> | null
metaDescription: string | null
metaTitle: string | null
keywords: string[]
faq: Array<{ question: string; answer: string }> | null
faqHtml?: string | null // metafield mode only
}
Modo metafield (Storefront API de Shopify)
Si ya consultas la entidad en la Storefront API, lee de ahí las señales de Clione:
const clione = createClioneSeo({ source: 'metafield' })
const signals = clione.extractSignals(product)
extractSignals lee:
clione.jsonld: el@graphcompleto que Clione propagó.faqse saca de su nodoFAQPage.clione.faq_html: el marcado del acordeón de FAQ visible.seo { title description }: el meta title y la descripción que Clione escribió englobal.title_tag/global.description_tag. La Storefront API no devuelve el namespaceglobala través demetafields(identifiers:), así que consultaseoen su lugar. En colecciones y páginas, el meta title solo se escribe si Publicar meta_title en categorías, colecciones y páginas está activado (Ajustes de la tienda → General); en productos se escribe siempre.
Productos, colecciones y páginas llevan todos estos metafields. Con @clione/seo/remix, CLIONE_STOREFRONT_FIELDS es la selección GraphQL que pegas en tu query; mira Remix / Hydrogen.
Así te ahorras una segunda llamada de red. Útil cuando:
- Ya pagas el coste de traer la entidad de Shopify.
- Quieres señales coherentes con la versión de la entidad que acabas de pintar.
- Estás en el edge y quieres menos llamadas en abanico.
Adaptadores de framework
Next.js (App Router)
// app/products/[id]/page.tsx
import { getClioneSeoMetadata, fetchClioneSeo, getJsonLdScriptProps } from '@clione/seo/react'
const config = {
apiUrl: process.env.CLIONE_API_URL!,
apiKey: process.env.CLIONE_API_KEY!,
platform: 'shopify' as const,
storefrontHost: 'shop.example.com',
}
export async function generateMetadata({ params }: { params: { id: string } }) {
return getClioneSeoMetadata(params.id, config, 'product') // { title, description }
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const signals = await fetchClioneSeo('product', params.id, config)
const jsonLdProps = getJsonLdScriptProps(signals)
return (
<>
{jsonLdProps && <script {...jsonLdProps} />}
{/* rest of page */}
</>
)
}
Guía completa: guía de Next.js.
Remix / Shopify Hydrogen
@clione/seo/remix pinta las señales de Clione server-side a partir de los metafields, en la primera respuesta:
// app/routes/products.$handle.tsx
import { json, type LoaderFunctionArgs, type MetaFunction } from '@shopify/remix-oxygen'
import { useLoaderData } from '@remix-run/react'
import { CLIONE_STOREFRONT_FIELDS, ClioneFaq, ClioneJsonLd, clioneMeta, loadClioneSignals } from '@clione/seo/remix'
const PRODUCT_QUERY = `#graphql
query ClioneProduct($handle: String!) {
product(handle: $handle) {
id
title
handle
descriptionHtml
${CLIONE_STOREFRONT_FIELDS}
}
}
`
export async function loader({ params, context }: LoaderFunctionArgs) {
const { product } = await context.storefront.query(PRODUCT_QUERY, {
variables: { handle: params.handle },
})
if (!product) throw new Response('Not found', { status: 404 })
return json({ product, seo: loadClioneSignals(product) })
}
export const meta: MetaFunction<typeof loader> = ({ data }) =>
clioneMeta(data?.seo, { title: data?.product.title })
export default function ProductPage() {
const { product, seo } = useLoaderData<typeof loader>()
return (
<main>
<ClioneJsonLd signals={seo} />
<h1>{product.title}</h1>
<ClioneFaq signals={seo} />
</main>
)
}
| Export | Qué hace |
|---|---|
CLIONE_STOREFRONT_FIELDS | Selección GraphQL para product, collection o page: seo { title description } más los metafields clione.jsonld y clione.faq_html. |
loadClioneSignals(entity) | Helper para el loader. Modo metafield, sin llamada de red; devuelve JSON plano para useLoaderData(). |
clioneMeta(signals, fallback?) | Descriptores para el export meta() de una ruta: título (con fallback.title de respaldo), descripción si la hay, y la marca clione:enriched. |
<ClioneJsonLd signals nativeStructuredData? /> | El grafo de clione.jsonld tal cual, en un único <script type="application/ld+json">. Pasa nativeStructuredData (true, el HTML de tu breadcrumb como string, o { jsonLdBlocks: [tuJsonLdComoString] }) cuando la página ya declara un BreadcrumbList, y el de Clione se queda fuera. |
<ClioneHead signals fallbackTitle? /> | Título + descripción + marca + <ClioneJsonLd>, para un layout que pinta el <head> desde un componente. No lo combines con clioneMeta() o el título sale dos veces. |
<ClioneFaq signals heading? /> | El acordeón de FAQ visible: el marcado de clione.faq_html tal cual, o construido a partir de las parejas de faq. |
Guía completa: guía de Hydrogen.
Nuxt / Vue Storefront / Alokai
useClioneSchemaGraph trae el @graph completo (Organization + WebSite + BreadcrumbList + entidad + FAQPage) y lo inyecta con el useHead() de Nuxt durante el SSR. Pasa el propio useHead de Nuxt en la configuración:
<!-- pages/products/[id].vue -->
<script setup>
import { useClioneSchemaGraph } from '@clione/seo/nuxt'
const route = useRoute()
const runtime = useRuntimeConfig()
await useClioneSchemaGraph('product', route.params.id, route.fullPath, {
apiUrl: runtime.public.clione.apiUrl,
apiKey: runtime.clione.apiKey,
platform: 'bigcommerce',
useHead,
})
</script>
Categorías, colecciones y páginas usan la misma llamada con otro entityType. En una página sin entidad (home, búsqueda), pasa undefined en los dos y recibes Organization + WebSite.
useClioneSeo(entityOrId, config) inyecta en su lugar las señales de una sola entidad: meta title, descripción y el JSON-LD de la entidad. Pasa un id como string para el modo API (con entityType en la configuración, 'product' por defecto), o un objeto de entidad con metafields para el modo metafield.
Caché
La SEO Signals API responde con Cache-Control: public, max-age=3600, s-maxage=86400, stale-while-revalidate=86400. El SDK llama al fetch de la plataforma sin opciones de caché propias, así que cachea con la primitiva de tu framework (revalidate de Next.js, las estrategias de caché de Hydrogen, tu CDN).
TypeScript
El paquete trae sus tipos:
import type {
ClioneSeoConfig,
ClioneSeoSignals,
ClioneSeoApiResponse,
ClioneSeoSchemaGraph,
ProductWithMetafields,
SeoHeadTags,
} from '@clione/seo'
Resolución de problemas
Señales vacías y un aviso @clione/seo: API returned 401: API key incorrecta o ausente. Revisa CLIONE_API_KEY.
API returned 403: a la key le falta products:read; o tiene dominios permitidos y la petición no nombró ninguno (pon en storefrontHost un host de la lista, o actualiza la lista en Ajustes de la tienda → Claves API); o es una key browser usada desde un servidor: crea una key server.
API returned 404: entidad sin sincronizar, id incorrecto o platform incorrecta (recuerda que por defecto es bigcommerce).
El modo metafield devuelve señales vacías: el metafield clione.jsonld está vacío (la entidad no se ha propagado) o la query no lo selecciona. Usa CLIONE_STOREFRONT_FIELDS.