From 292c72b3e1c5d189c38eeb2bea2ca6d948ad9825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Antonio=20Mijares?= Date: Thu, 30 Apr 2026 12:45:35 -0600 Subject: [PATCH] =?UTF-8?q?Add=20nosto=20skill=20=E2=80=94=20Nosto=20front?= =?UTF-8?q?end=20JS=20APIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference for integrating Nosto's frontend SDK on a storefront. Covers the legacy nostojs global, the @nosto/nosto-js npm package, and @nosto/nosto-react. SKILL.md is lean: triggers, the Page Tagging vs Session API choice, and the most common patterns inline. Detailed material is split into five reference files for progressive disclosure: - references/initialization.md — loaders, init() options, consent gating - references/session-events.md — defaultSession chain, viewX, addOrder - references/recommendations.md — placements, dynamic filtering, callbacks - references/customer-search-experiments.md — customer ID, GraphQL search - references/patterns-gotchas.md — SPA, multi-store, common bugs Sources: docs.nosto.com/techdocs and nosto.github.io/nosto-js. --- README.md | 23 +++ skills/nosto/SKILL.md | 143 ++++++++++++++ skills/nosto/metadata.json | 16 ++ .../references/customer-search-experiments.md | 129 ++++++++++++ skills/nosto/references/initialization.md | 117 +++++++++++ skills/nosto/references/patterns-gotchas.md | 121 ++++++++++++ skills/nosto/references/recommendations.md | 153 ++++++++++++++ skills/nosto/references/session-events.md | 186 ++++++++++++++++++ 8 files changed, 888 insertions(+) create mode 100644 skills/nosto/SKILL.md create mode 100644 skills/nosto/metadata.json create mode 100644 skills/nosto/references/customer-search-experiments.md create mode 100644 skills/nosto/references/initialization.md create mode 100644 skills/nosto/references/patterns-gotchas.md create mode 100644 skills/nosto/references/recommendations.md create mode 100644 skills/nosto/references/session-events.md diff --git a/README.md b/README.md index f6fae93e..92d01d48 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,29 @@ Preview URL: https://skill-deploy-abc123.vercel.app Claim URL: https://vercel.com/claim-deployment?code=... ``` +### nosto + +Frontend integration guide for [Nosto](https://www.nosto.com/), the e-commerce personalization platform. Covers the `nostojs` global, the `@nosto/nosto-js` npm package, and `@nosto/nosto-react`. + +**Use when:** +- Implementing or debugging Nosto on a storefront +- Migrating from Page Tagging to the Session API (SPA / headless) +- Sending Nosto events (`viewProduct`, `viewCategory`, `viewCart`, `viewSearch`, `addOrder`) +- Rendering recommendation placements and wiring add-to-cart attribution +- Integrating Nosto into a React / Next.js app with `@nosto/nosto-react` +- Gating Nosto behind GDPR / cookie consent + +**Topics covered:** +- Initialization (`init`, the legacy stub, custom script loaders, Shopify Markets) +- Session API chain (`defaultSession().viewX(...).setPlacements([...]).load()`) +- Order tracking and conversion attribution +- Recommendation placements, dynamic filtering, `prerender` / `postrender` callbacks +- Customer identification (with PII / `customer_reference` rules) +- Nosto Search GraphQL +- Common gotchas: mixing Page Tagging with Session API, double-tracking, empty placements, SSR limitations + +**Sources:** [docs.nosto.com/techdocs](https://docs.nosto.com/techdocs), [nosto-js typedoc](https://nosto.github.io/nosto-js/modules.html). + ## Installation ```bash diff --git a/skills/nosto/SKILL.md b/skills/nosto/SKILL.md new file mode 100644 index 00000000..f82673dc --- /dev/null +++ b/skills/nosto/SKILL.md @@ -0,0 +1,143 @@ +--- +name: nosto +description: Nosto frontend JavaScript SDK and `@nosto/nosto-js` package — initialization, session events (viewProduct, viewCategory, viewCart, viewSearch, addOrder), recommendation placements, cart and customer tracking, search, and SPA/React integration. Use when implementing or debugging Nosto on a storefront, integrating product recommendations, sending Nosto events, wiring Nosto into a React/Next.js/headless app, or working with the `nostojs` global, the `@nosto/nosto-js` npm package, or `@nosto/nosto-react`. Triggers on mentions of Nosto, `nostojs`, `nosto-js`, Nosto recommendations, Nosto placements, or Nosto session API. +license: MIT +metadata: + author: community + version: "1.0.0" +--- + +# Nosto Frontend JS APIs + +Reference for integrating Nosto's frontend JavaScript SDK on a storefront. Covers the legacy `nostojs` global, the modern `@nosto/nosto-js` npm package, the `@nosto/nosto-react` provider, and the underlying session/recommendation APIs. + +## When to Apply + +- Adding Nosto to a Shopify, BigCommerce, Magento, or custom storefront +- Migrating from Page Tagging to the Session API (SPA/headless) +- Sending Nosto events (`viewProduct`, `viewCategory`, `viewCart`, `viewSearch`, `addOrder`) +- Rendering recommendation placements and attributing add-to-cart events +- Wiring Nosto into React/Next.js with `@nosto/nosto-react` +- Implementing GDPR/cookie-consent gating around the Nosto loader +- Debugging empty placements, double-tracking, or broken attribution + +## Two Integration Modes — Pick One Per Page + +| Mode | Use it when | Key calls | +|---|---|---| +| **Page Tagging** | Server-rendered storefront with `
` tags in the DOM | The script auto-reads tagging; `api.customer(...)`, `api.resendCartTagging()` | +| **Session API** | SPA, headless, or any app where you build state imperatively per route | `api.defaultSession().viewX(...).setPlacements([...]).load()` | + +**Never mix both on the same page** — produces double-tracked sessions or empty placements. See `references/patterns-gotchas.md`. + +## Quick Reference + +### 1. Load the script + +Modern (recommended): + +```ts +import { init, nostojs } from "@nosto/nosto-js"; +await init({ merchantId: "shopify-12345" }); +``` + +Legacy embed (when modifying a theme directly): + +```html + + +``` + +Full loader patterns, custom script loaders, Shopify International, and consent-gated loading: `references/initialization.md`. + +### 2. Session API — every route change + +```js +nostojs(api => + api.defaultSession() + .viewProduct("product-id") + .setPlacements(["productpage-nosto-1", "productpage-nosto-2"]) + .load() + .then(res => console.log(res.recommendations)) +); +``` + +Replace `viewProduct` with the page-type method: + +| Page | Method | +|---|---| +| Front page | `viewFrontPage()` | +| Category | `viewCategory("/path")` | +| Product | `viewProduct(productId)` | +| Cart | `viewCart()` | +| Search | `viewSearch(query)` | +| 404 / content / account | `viewOther()` | + +Full session chain, `setCustomer`, `addOrder`, dynamic filtering, and recommendation callbacks: `references/session-events.md` and `references/recommendations.md`. + +### 3. Order tracking — once on the confirmation page + +```js +nostojs(api => + api.defaultSession() + .addOrder({ + external_order_ref: "145000006", + info: { order_number: "195", email: "buyer@example.com", + first_name: "X", last_name: "Y", type: "order", newsletter: true }, + items: [{ product_id: "406", sku_id: "243", name: "Item", + quantity: 1, unit_price: 49.5, price_currency_code: "USD" }] + }) + .setPlacements(["order-related"]) + .load() +); +``` + +Idempotency relies on `external_order_ref`. Full schema in `references/session-events.md`. + +### 4. Add-to-cart attribution + +```js +nostojs(api => api.recommendedProductAddedToCart(productId, "nosto-categorypage-1")); +``` + +Records *attribution only*. Cart contents must also be updated (Page Tagging: `api.resendCartTagging()`; Session API: re-call `viewCart()` or include the new cart in the next session). + +### 5. React / Next.js + +```tsx +import { NostoProvider, NostoSession } from "@nosto/nosto-react"; + + + + + +``` + +Routing patterns and SPA pitfalls: `references/patterns-gotchas.md`. + +## References (load on demand) + +- `references/initialization.md` — loaders, `init()` options, consent gating, debug mode +- `references/session-events.md` — full `defaultSession` chain, every `view*`, `addOrder`, `setCustomer`, cart updates +- `references/recommendations.md` — `setPlacements`, `loadRecommendations`, `createRecommendationRequest`, dynamic filtering, prerender/postrender callbacks +- `references/customer-search-experiments.md` — customer identification, GraphQL search, A/B testing surfaces +- `references/patterns-gotchas.md` — SPA integration, multi-store, GDPR, common bugs (double-tracking, missing attribution, PII in `customer_reference`) + +## TypeScript Module Map + +The `@nosto/nosto-js` typedoc lives at and exposes two top-level modules: + +- `core` — `init`, `nostojs`, `isNostoLoaded`, `getSettings`, `getNostoWindow`, `addSkuToCart`, types `InitProps`, `Settings`, `BackendEnvironment`, `ScriptLoadOptions` +- `client` — the runtime API surface: `API`, `Session`, `Cart`, `Order`, `Customer`, `Product`, `JSONProduct`, `SearchQuery`, `SearchResult`, `ABTest`, `Experiment`, `RenderMode`, `InsertMode`, etc. + +When writing TypeScript, import types from `@nosto/nosto-js/client` for runtime shapes and from `@nosto/nosto-js` for `init`/`nostojs`. + +## Authoritative Sources + +- Tech docs: +- JS APIs: +- Typedoc: +- Source: diff --git a/skills/nosto/metadata.json b/skills/nosto/metadata.json new file mode 100644 index 00000000..1bea949a --- /dev/null +++ b/skills/nosto/metadata.json @@ -0,0 +1,16 @@ +{ + "version": "1.0.0", + "organization": "Community", + "date": "April 2026", + "abstract": "Reference for Nosto's frontend JavaScript SDK and the @nosto/nosto-js npm package. Covers script loading, the session API (viewProduct, viewCategory, viewCart, viewSearch, addOrder), recommendation placements and attribution, customer identification, GraphQL search, GDPR/consent gating, React/Next.js integration via @nosto/nosto-react, and common gotchas (double-tracking, mixing Page Tagging with Session API, PII in customer_reference). Designed for AI agents implementing or debugging Nosto on storefronts.", + "references": [ + "https://docs.nosto.com/techdocs", + "https://docs.nosto.com/techdocs/apis/frontend/js-apis", + "https://docs.nosto.com/techdocs/apis/frontend/implementation-guide-session-api", + "https://nosto.github.io/nosto-js/modules.html", + "https://nosto.github.io/nosto-js/modules/client.html", + "https://nosto.github.io/nosto-js/modules/core.html", + "https://github.com/Nosto/nosto-js", + "https://github.com/Nosto/nosto-react" + ] +} diff --git a/skills/nosto/references/customer-search-experiments.md b/skills/nosto/references/customer-search-experiments.md new file mode 100644 index 00000000..eb6109ff --- /dev/null +++ b/skills/nosto/references/customer-search-experiments.md @@ -0,0 +1,129 @@ +# Nosto — Customer, Search & Experiments + +## Customer identification + +Two equivalent forms — pick by integration mode. + +**Session API (chainable):** + +```js +nostojs(api => + api.defaultSession() + .setCustomer({ + customer_reference: "b369f1235cf4f08153c560.82515936", + email: "buyer@example.com", + first_name: "Nosto", + last_name: "Test", + newsletter: true, + marketing_permission: true + }) + .viewCart() + .update() +); +``` + +**Page Tagging (one-shot):** + +```js +nostojs(api => + api.customer({ + email: "jane.doe@example.com", + first_name: "Jane", + last_name: "Doe", + marketing_permission: true, + customer_reference: "5e3d4a9c-cf58-11ea-87d0-0242ac130003" + }) +); +``` + +### Field rules + +| Field | Required | Notes | +|---|---|---| +| `customer_reference` | Yes | Stable, pseudonymous ID. UUID or hashed user ID. **Never raw email or PII.** | +| `email` | No | Goes in this field, not in `customer_reference` | +| `marketing_permission` | For triggered email | Explicit GDPR opt-in. Without it, contact is treated as opted-out of triggered messages. | +| `newsletter` | For triggered email | Equivalent on the order-level `info` object | + +## Search — Nosto Search GraphQL + +Search is exposed via GraphQL at `https://search.nosto.com/v1/graphql`. The client SDK ships TypeScript types but not a built-in fetcher — wire your own client (urql, Apollo, fetch): + +```graphql +query Storefront($q: String!) { + search( + accountId: "shopify-12345" + query: $q + products: { size: 24 } + keywords: { size: 5 } + categories: { size: 5 } + popularSearches: { size: 5, emptyQueryMatchesAll: true } + ) { + products { + hits { productId name listPrice price imageUrl url } + total + fuzzy + } + keywords { + hits { keyword _redirect _highlight { keyword } } + } + categories { + hits { name url urlPath } + total + } + popularSearches { + hits { query total } + total + } + } +} +``` + +```ts +const res = await fetch("https://search.nosto.com/v1/graphql", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query: STOREFRONT_QUERY, + variables: { q: input } + }) +}).then(r => r.json()); + +const fuzzy = res.data.search.products.fuzzy; // boolean — populate analytics autoCorrect +``` + +### Relevant types from `@nosto/nosto-js/client` + +`SearchQuery`, `SearchResult`, `SearchProduct`, `InputSearchFilter`, `InputSearchFacetConfig`, `SearchAutocorrect`, `SearchExplain`. + +### Autocomplete + +Use Nosto's autocomplete component (configured in admin) rather than calling GraphQL directly for the dropdown — it handles rendering, debouncing, keyboard nav, and analytics. Drop-in via the embed script; no extra code needed beyond placing the `data-nosto-search` attribute on your input. + +## Experiments / A-B testing + +Most experiments are configured in the Nosto admin and surfaced through standard placement responses — no bespoke client code is needed. + +When you DO need to read or override variations from the client, the relevant types in `@nosto/nosto-js/client` are: + +- `ABTest` — admin-defined test definition +- `Experiment` — currently-running experiment instance +- `Campaign` — single campaign (placement) inside an experiment +- `ForcedTestDTO` — admin-forced variation for QA +- `TestPreviewsDTO` — preview data when entering preview mode + +### Reading the active variation in callbacks + +```js +nostojs(api => { + api.listen("prerender", event => { + event.segments.forEach(segmentId => console.log("In segment:", segmentId)); + }); +}); +``` + +A/B variations show up as segment IDs on `prerender`. Conditional UI based on segment membership is the standard pattern. + +### Forcing a variation (QA / preview) + +Append `?nostodebug=true` to the URL and use the debug toolbar's "Test previews" panel to force a variation. There is no production-safe way to force variations from JS — that's by design (forcing would skew test results). diff --git a/skills/nosto/references/initialization.md b/skills/nosto/references/initialization.md new file mode 100644 index 00000000..d1cdceb7 --- /dev/null +++ b/skills/nosto/references/initialization.md @@ -0,0 +1,117 @@ +# Nosto — Initialization & Loading + +## Modern: `@nosto/nosto-js` + +```bash +npm install @nosto/nosto-js +``` + +```ts +import { init, nostojs, isNostoLoaded, getSettings } from "@nosto/nosto-js"; + +await init({ + merchantId: "shopify-12345", + // Optional fields below + env: "production", // BackendEnvironment + options: { /* ScriptLoadOptions */ }, // forwarded to script loader + scriptLoader: (src, opts) => myCustomLoader(src), + shopifyInternational: { language: "en", marketId: 1 }, +}); + +// Run code once the client API is ready (callback is queued if init is still loading): +nostojs(async api => { + console.log(isNostoLoaded(), getSettings()); +}); +``` + +`init()` resolves once the embed script has loaded. `nostojs(cb)` queues `cb` if the script isn't ready yet, then runs it with the `client.API` object. + +### `InitProps` (key fields) + +| Field | Type | Purpose | +|---|---|---| +| `merchantId` | `string` | Account ID from Nosto admin, e.g. `shopify-12345` | +| `env` | `"production" \| "staging"` | Backend environment | +| `options` | `ScriptLoadOptions` | Forwarded to the script loader (CSP nonce, attributes) | +| `scriptLoader` | `(src, opts) => Promise` | Override default loader (use for nonces, custom CDNs) | +| `shopifyInternational` | `{ language, marketId }` | Shopify Markets multi-market resolution | + +## Legacy: embed ` + + + + + + +``` + +The stub MUST come before the async script so that any pre-script `nostojs(...)` calls survive. Don't call `window.nosto.*` directly — those are internals and can change between releases. + +## Cookie consent / GDPR + +Three valid patterns, in order of preference: + +### 1. Don't load Nosto until consent + +The cleanest option — the embed script never runs, no `2c.cId` cookie is set. + +```ts +if (cookieConsent.marketing) { + await init({ merchantId: "shopify-12345" }); +} +``` + +### 2. Load with tracking disabled, enable on consent + +Use when the script must load early (e.g. for non-tracking features) but tracking gates on consent: + +```js +nostojs(api => api.visit.setDoNotTrack(true)); + +// Later, after the visitor accepts marketing cookies: +cookieConsent.on("accept", () => { + nostojs(api => api.visit.setDoNotTrack(false)); +}); +``` + +`setDoNotTrack(true)` BEFORE any tracking call prevents the visitor cookie from being set. Toggling later does not retroactively delete an already-set cookie. + +### 3. Per-event opt-out via `marketing_permission` / `newsletter` + +For triggered email/push, the visitor must have consented. Pass `marketing_permission: true` on `customer()` or `setCustomer()`, and `newsletter: true` inside `addOrder().info`. Without these flags, Nosto treats the contact as opted-out for triggered messages even if the cookie is set. + +## Auto-load control + +By default, the client auto-fires the recommendation request after DOMContentLoaded. To take manual control: + +```js +nostojs(api => api.setAutoLoad(false)); + +// Later, when the DOM and session are ready: +nostojs(api => api.load()); +``` + +Use this for SPAs, deferred hydration, or when the cart/customer data is fetched async. + +## Debug toolbar + +Append `?nostodebug=true` to any storefront URL (use an incognito window). The toolbar shows fired events, resolved placements, segments, A/B variations, and tagging detection. Use this to verify integrations before going live. + +## Multi-store + +One Nosto implementation per store. Account IDs are platform-prefixed (`shopify-12345`, `bigcommerce-67890`). For Shopify Markets, pass `shopifyInternational: { language, marketId }` so Nosto resolves the correct market catalog. diff --git a/skills/nosto/references/patterns-gotchas.md b/skills/nosto/references/patterns-gotchas.md new file mode 100644 index 00000000..b7e5cbe0 --- /dev/null +++ b/skills/nosto/references/patterns-gotchas.md @@ -0,0 +1,121 @@ +# Nosto — Integration Patterns & Gotchas + +## React / Next.js — `@nosto/nosto-react` + +```bash +npm install @nosto/nosto-react @nosto/nosto-js +``` + +```tsx +import { NostoProvider, NostoSession, NostoPlacement } from "@nosto/nosto-react"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +Per-page session calls: + +```tsx +import { NostoHome, NostoProduct, NostoCategory } from "@nosto/nosto-react"; + +// Home + + +// Product + + +// Category + +``` + +The provider re-fires the session chain whenever the inner component remounts (e.g. on Next.js route change in App Router). For App Router specifically, mount these in **client components** — `NostoProvider` uses browser-only globals. + +### Custom rendering (JSON mode) + +```tsx +import { useNosto } from "@nosto/nosto-react"; + +function CustomRecs() { + const { recommendations } = useNosto(); + const placement = recommendations?.["productpage-nosto-1"]; + if (!placement) return null; + return ; +} +``` + +## SPA route-change checklist + +On every route change you must: + +1. Build the new session: `defaultSession().viewX(...)` +2. Set placements present on the new page: `.setPlacements([...])` +3. Trigger fetch: `.load()` + +Without `load()` the page will display stale recommendations. The router's success hook (Next.js `useEffect` on `pathname`, React Router `useLocation`, etc.) is the right place. + +## Common gotchas + +### 1. Mixing Page Tagging and Session API on the same page + +Pick ONE per page. Page Tagging expects `
` tags in the DOM and reads them automatically. Session API expects nothing in the DOM and you build state imperatively. Mixing produces double-tracked sessions or empty placements. + +### 2. Calling `window.nosto.*` directly + +Don't. Always go through `nostojs(api => ...)`. The `window.nosto` namespace is internal and breaks across releases. The stub queue exists specifically to bridge pre-load and post-load calls. + +### 3. PII in `customer_reference` + +`customer_reference` MUST be a stable pseudonymous ID — UUID, hashed user ID, anything non-reversible. Putting raw email there is a privacy bug and will produce duplicate visitor records (Nosto already has `email` as a separate field). + +### 4. Forgetting attribution + +`recommendedProductAddedToCart()` records *attribution only*. You also need to: + +- (Page Tagging) update cart tagging via `setTaggingProvider("cart", ...)` + `resendCartTagging()`, OR +- (Session API) re-call `viewCart()` with the new cart on `setCart()`. + +Skipping the cart-content update means the cart appears empty in Nosto's view even though attribution recorded. + +### 5. Calling `api.load()` before the DOM is ready + +When you've turned off auto-load with `setAutoLoad(false)`, only call `api.load()` after the page is rendered AND your session has the correct page-type method on it. Calling earlier produces empty placements (Nosto returns recs but there's no `
` to inject into yet). + +### 6. Double-firing `addOrder()` + +`addOrder()` must run once per confirmation page. Idempotency relies on `external_order_ref` — but only at the backend level. Re-running on the same page (e.g. via React StrictMode double-mount) will fire two requests; the backend dedupes but you've doubled your network spend and skewed timing data. Gate with a ref/flag in React. + +### 7. Wrong `merchantId` per market + +Per-store account IDs only work for that store's catalog. Sending `shopify-us-12345` to a UK storefront silently produces empty recs because no products match. For Shopify Markets, pass `shopifyInternational: { language, marketId }` so Nosto resolves the right market catalog automatically. + +### 8. GDPR cookie set before consent + +Once `2c.cId` is set, opting out doesn't retroactively delete it. Either: + +- don't load the script until consent, or +- call `nostojs(api => api.visit.setDoNotTrack(true))` BEFORE any tracking call (i.e. in the same script tag, before any other `nostojs(...)` callbacks). + +### 9. SSR + personalization + +`defaultSession()` is client-only. Don't try to call it during SSR. For SSR personalization, use Nosto's GraphQL `updateSession()` mutation server-side then re-sync the client. GraphQL session updates do NOT support OCP (on-site content personalization), dynamic filtering, or built-in A/B testing — those require the JS client. + +### 10. Empty placement bodies in production + +Three things to check, in order: + +1. Did `setPlacements([...])` actually include the placement ID? (typo? mismatch with admin?) +2. Did `.load()` run? (missed route hook? `setAutoLoad(false)` without a follow-up `load()`?) +3. Does the placement have products in admin given the current segment/filters? (Use `?nostodebug=true` to inspect.) + +## Debugging recipe + +1. Open the page in incognito with `?nostodebug=true` appended +2. Check the toolbar's "Events" tab — should show one of `viewProduct`/`viewCategory`/etc. firing +3. Check "Placements" tab — green = filled, red = empty +4. If empty: open the placement in admin, check its targeting rules and product feed +5. If event missing: your Session API chain isn't running on this route — verify the router hook diff --git a/skills/nosto/references/recommendations.md b/skills/nosto/references/recommendations.md new file mode 100644 index 00000000..fe108791 --- /dev/null +++ b/skills/nosto/references/recommendations.md @@ -0,0 +1,153 @@ +# Nosto — Recommendations & Placements + +Placements are named slots in the page (e.g. `frontpage-nosto-1`, `productpage-nosto-3`) configured in the Nosto admin. The client requests one or more placements per page and Nosto returns recommendation HTML/JSON for each. + +## Declaring placements + +The Session API expects placement IDs: + +```js +nostojs(api => + api.defaultSession() + .viewFrontPage() + .setPlacements(["frontpage-nosto-1", "frontpage-nosto-2"]) + .load() +); +``` + +Or read every placement currently in the DOM (Page Tagging mode or hybrid): + +```js +nostojs(api => api.placements.getPlacements()); // string[] +``` + +## `setAutoLoad(false)` + manual `load()` + +By default, the client auto-loads after DOMContentLoaded. To delay (e.g. waiting for hydration or consent): + +```html + +``` + +```js +// When ready: +nostojs(api => api.load()); +``` + +## Re-rendering after a route change (`loadRecommendations`) + +In a quasi-SPA where a partial page swap happens but you don't want to rebuild the whole session: + +```js +nostojs(api => api.loadRecommendations()); +``` + +Pass attribution metadata when re-rendering due to a click on an existing rec (e.g. opening a quick-view modal): + +```js +nostojs(api => + api.loadRecommendations({ markNostoElementClicked: "nosto-categorypage-1" }) +); +``` + +## Imperative requests outside the session chain — `createRecommendationRequest` + +Use when you need fine-grained control: extra tags, custom fields, restricted to specific elements. + +```js +nostojs(api => { + api.createRecommendationRequest({ includeTagging: true }) + .setCurrentTags(["color:red", "season:fall"]) + .addCurrentCustomFields({ gender: "male", material: "cotton" }) + .setElements(["productpage-nosto-3"]) + .load() + .then(response => console.log(response)); +}); +``` + +Quick-view of a specific product/SKU variant: + +```js +nostojs(api => + api.createRecommendationRequest({ includeTagging: true }) + .setProducts([{ product_id: "6961338417345", selected_sku_id: "40822930473153" }]) + .load() +); +``` + +## Dynamic filtering + +Pass per-request filter values to a placement: + +```js +nostojs(api => + api.createRecommendationRequest({ includeTagging: true }) + .setElements(["category-recs"]) + .addCurrentCustomFields({ + in_stock: "true", + price_max: "100" + }) + .load() +); +``` + +The placement must be configured in the Nosto admin to expose those filter fields. + +## Lifecycle callbacks — `prerender` / `postrender` + +```js +nostojs(api => { + api.listen("prerender", event => { + // Fired BEFORE recommendations are inserted into the DOM + console.log(event.affinityScores); // { brands: [{name, score}], categories: [...] } + console.log(event.segments); // string[] — segment IDs + }); + + api.listen("postrender", event => { + // Fired AFTER recommendations are inserted + console.log(event.filledElements); // string[] — placement IDs that got recs + console.log(event.unFilledElements); // string[] — placement IDs that came back empty + }); +}); +``` + +Use `prerender` to add segment-based DOM tweaks before the recs are visible. Use `postrender` to swap layouts or hide empty wrappers based on `unFilledElements`. + +## `RenderMode` and `InsertMode` + +The `client` module exposes `RenderMode` (`"HTML" | "JSON_ORIGINAL" | "JSON_REDUCED"`) and `InsertMode` (`"REPLACE" | "APPEND" | "PREPEND" | ...`). These are configured per placement in the Nosto admin; in client code you typically only inspect them when handling JSON responses: + +```js +nostojs(api => + api.defaultSession() + .viewProduct("p1") + .setPlacements(["json-placement"]) + .setResponseMode("JSON_ORIGINAL") + .load() + .then(({ recommendations }) => { + // recommendations[placementId] = { products: [...], title, etc. } + const placement = recommendations["json-placement"]; + renderInReact(placement.products); + }) +); +``` + +`JSON_ORIGINAL` returns the full product objects (use this in React/headless). `HTML` returns server-rendered markup that the client injects into `
`. + +## Attribution + +Every recommendation click/add-to-cart should be attributed back to its placement so reports work: + +```js +// On add-to-cart from a Nosto rec: +nostojs(api => + api.recommendedProductAddedToCart(productId, "nosto-categorypage-1") +); + +// On click attribution for navigation (rec → PDP): +nostojs(api => + api.loadRecommendations({ markNostoElementClicked: "nosto-categorypage-1" }) +); +``` + +Without these calls, Nosto's "revenue from recommendations" report will be empty. diff --git a/skills/nosto/references/session-events.md b/skills/nosto/references/session-events.md new file mode 100644 index 00000000..d7841963 --- /dev/null +++ b/skills/nosto/references/session-events.md @@ -0,0 +1,186 @@ +# Nosto — Session API & Events + +The Session API is the imperative way to tell Nosto about a page view. Use it on SPAs, headless storefronts, or any place where Page Tagging isn't appropriate. + +**Pattern:** `api.defaultSession().().setPlacements([...]).load()` + +`defaultSession()` returns a chainable `Session` builder. Every method on it returns the same builder (except terminal `load()`, which returns a `Promise`). Run the whole chain on every route change. + +## Page-type methods + +| Method | Args | Purpose | +|---|---|---| +| `viewFrontPage()` | — | Home / landing page | +| `viewCategory(path)` | `string` (e.g. `"/Women/Dresses"`) | Category listing | +| `viewProduct(productId)` | `string` | Product detail | +| `viewCart()` | — | Cart page | +| `viewSearch(query)` | `string` | Search results | +| `viewOther()` | — | 404, content, account, anything else | + +```js +nostojs(api => + api.defaultSession() + .viewCategory("/Womens/Dresses") + .setPlacements(["category-related"]) + .load() +); +``` + +Use `api.placements.getPlacements()` to read every placement ID currently declared in the page (handy on the front page where placements vary): + +```js +nostojs(api => + api.defaultSession() + .viewFrontPage() + .setPlacements(api.placements.getPlacements()) + .load() +); +``` + +## Cart & customer context on the session + +```js +nostojs(api => + api.defaultSession() + .setCart({ + items: [{ + product_id: "406", + sku_id: "243", + name: "Linen Blazer", + quantity: 1, + unit_price: 49.5, + price_currency_code: "USD" + }] + }) + .setCustomer({ + customer_reference: "5e3d4a9c-cf58-11ea-87d0-0242ac130003", + email: "buyer@example.com", + first_name: "Jane", + last_name: "Doe", + newsletter: true, + marketing_permission: true + }) + .viewCart() + .setPlacements(["cart-related"]) + .load() +); +``` + +`customer_reference` MUST be a stable pseudonymous ID — never the raw email. Email goes in `email`. Hashed IDs or UUIDs are fine. + +## Order tracking — `addOrder()` + +Call once on the order-confirmation page. Records the conversion and produces order-page recommendations. + +```js +nostojs(api => + api.defaultSession() + .addOrder({ + external_order_ref: "145000006", + info: { + order_number: "195", + email: "buyer@example.com", + first_name: "Jane", + last_name: "Doe", + type: "order", + newsletter: true + }, + items: [{ + product_id: "406", + sku_id: "243", + name: "Linen Blazer (White, S)", + quantity: 1, + unit_price: 455, + price_currency_code: "EUR" + }] + }) + .setPlacements(["order-related"]) + .load() +); +``` + +Idempotency relies on `external_order_ref` — re-firing with the same ref is a no-op on the backend. Don't omit it. + +## Cart updates mid-session (Page Tagging) + +When the cart changes without a full page reload (mini-cart, ajax add): + +```js +nostojs(api => { + fetch("/cart.json") + .then(r => r.json()) + .then(cart => { + const taggingHtml = renderCartTagging(cart); // your function + api.setTaggingProvider("cart", taggingHtml); + api.resendCartTagging(); + }); +}); +``` + +For Session API mode, just call the next session chain with the new cart on `setCart()` — there's no separate "resend". + +## Cart updates mid-session (Session API) + +```js +nostojs(api => + api.defaultSession() + .setCart({ items: newItems }) + .viewCart() // or whatever page the user is on + .update() // re-evaluates without re-fetching recommendations +); +``` + +Use `.update()` (not `.load()`) when you only need to update session state without re-rendering placements. + +## Add-to-cart attribution + +```js +nostojs(api => + api.recommendedProductAddedToCart("productId1", "nosto-categorypage-1") +); +``` + +Alias: `reportAddToCart`. Records *attribution only*. You still need to update cart contents (above) for the cart to reflect the actual state. + +## Common `Cart`/`Order`/`Customer` field reference + +### `CartItem` + +```ts +{ + product_id: string; + sku_id: string; + name: string; + quantity: number; + unit_price: number; + price_currency_code: string; // ISO 4217, e.g. "USD" +} +``` + +### `OrderInfo` + +```ts +{ + order_number: string; + email: string; + first_name: string; + last_name: string; + type: "order"; + newsletter?: boolean; // GDPR opt-in for triggered email +} +``` + +### `Customer` + +```ts +{ + customer_reference: string; // stable, non-PII pseudonymous ID + email?: string; + first_name?: string; + last_name?: string; + newsletter?: boolean; + marketing_permission?: boolean; // explicit GDPR opt-in +} +``` + +Full TypeScript types: .