Skip to main content
The Remix adapter covers product pages and the sitemap for Remix v2. (Collection and site-level helpers are currently App Router only — use the core fetch helpers if you need them here.) Import from @agentshop/seo/remix.
On Shopify Hydrogen/Oxygen, use @agentshop/seo/hydrogen instead — Oxygen has no process.env, and the Hydrogen adapter reads your environment from context.env.

Product routes

Load the bundle in your loader, then export the ready-made meta function:
app/routes/products.$handle.tsx
  • loadProductSeo(handle, config?) returns { agentshopSeo: SeoBundle | null } — spread it into your loader data under that exact key.
  • agentshopProductMeta(args) is a Remix v2 meta function that reads data.agentshopSeo and emits the title, description, canonical, Open Graph (including og:type=product), Twitter tags, and the JSON-LD via a "script:ld+json" descriptor. If the bundle is null, it returns an empty array and your route renders without tags.
Remix renders the "script:ld+json" descriptor with a plain JSON.stringify — it does not escape <. Since @agentshop/seo 0.3.2, the SDK therefore omits the JSON-LD descriptor whenever the bundle’s JSON-LD contains any < character (a </script> in a product title would otherwise break out of the script tag), and logs a warning once per page naming the affected URL. A product whose title or description contains a bare < (e.g. Widget < 5kg) loses its structured data on Remix and Hydrogen until the character is removed or written as &lt;; every other meta tag is unaffected. See Security.
If you have your own meta function, compose instead of replacing:

Sitemap

Create a resource route:
app/routes/[sitemap.xml].ts
The loader returns 200 XML with Cache-Control: public, max-age=3600, stale-while-revalidate=86400, or 502 with an empty body when the sitemap is unavailable.

Configuration

All three exports accept an optional SeoClientConfig; AGENTSHOP_API_KEY and AGENTSHOP_SEO_URL are read from process.env for anything you don’t pass. AGENTSHOP_API_KEY is a secret — it’s only read in loaders (server-side), so keep it in a server-side env var, never one exposed to the browser. All helpers follow the never-throw contract.