Skip to main content
The core entry point (@agentshop/seo) has zero runtime dependencies and works in any server environment with global fetch (Node ≥ 18, edge runtimes, workerd). All fetch helpers follow the never-throw contract: null on any failure, never an exception.

Fetch helpers

Fetches the product bundle for a product handle.
Fetches the collection bundle. Structurally identical to a product bundle; openGraph.type is "website".
Fetches the site-level JSON-LD graph (Organization + WebSite + optional FAQPage).
Fetches the product sitemap as an XML string.
Fetches where to load the storefront analytics script from — { scriptUrl, token, eventsUrl, shopDomain }. Resolve it per render rather than storing the values: the pixel token can change, and one frozen in a deployed build stops working silently. See Storefront analytics.
Bot-checks a standard Request and reports it if it’s a crawler — the whole recipe in one call: the isbot gate, stripping url and referrer to origin + pathname, and the POST. Resolves false when the request isn’t a bot, the URL is unreportable, or delivery failed; it never throws and never blocks the response. Pass statusCode on a runtime that performed the fetch itself (a Cloudflare Worker). The Next.js proxy wrapper omits it deliberately — see AI crawler visibility.

Meta builders

Builds the Remix/Hydrogen v2 meta descriptor array from a bundle: title, description, canonical, Open Graph (including og:type=product), Twitter tags, and the JSON-LD via a "script:ld+json" descriptor. Returns [] for a null bundle. For the bundle → Next.js Metadata mapper, use toNextMetadata. It is not a core export — it ships only from @agentshop/seo/next, so non-Next consumers (Remix, Hydrogen, plain Node) aren’t forced to install next to typecheck.

Serialization

Serializes JSON-LD for embedding inside a <script type="application/ld+json"> tag, escaping every < to its JSON unicode form (\u003c) so a data field containing </script> (product titles, vendor names, FAQ answers…) can’t break out of the script block. The result is still valid JSON. Always use this instead of JSON.stringify when writing your own script tags — see Security.

Validation

The throwing configuration validator — see Configuration.

Types

SeoClientConfig is documented in Configuration.