Components
The Next.js package ships the same components as the Nuxt layer, ported to React. Import the stylesheet once, in the root layout:
import '@avvyr/storefront-next/styles.css'
The styles use BEM classes (avvyr-consent__card, avvyr-assistant__panel and so on) and CSS variables, so you can theme or override them with your own CSS.
<AvvyrCookieConsent />
The cookie banner. Place it in the layout:
import { AvvyrCookieConsent } from '@avvyr/storefront-next'
<AvvyrCookieConsent />
The visitor's choice is stored in the avvyr_consent cookie, so the server and the browser render the same banner. Saving a choice:
- grants or revokes Avvyr tracking (identity cookies are only set after consent),
- logs a receipt with the platform (append-only evidence log, GDPR art. 7.1),
- sends a Google Consent Mode v2 update to the dataLayer, followed by the
analytics.consentUpdateEventevent. - stores the choice on the cart (
useCart().setConsent). It follows the cart onto the order and decides whether the purchase may be sent server-side to marketing channels. A cart created later picks up the cookie by itself.
With your own consent manager (CookieFirst, Cookiebot), call useCart().setConsent({ analytics, marketing }) when the visitor saves a choice, so the order still knows what they agreed to.
Configure it under consent in storefront.config.ts: enabled, policyVersion (bump it to ask every visitor again), privacyPolicyPath, blocking (a centred modal that requires a choice; rejecting stays as easy as accepting), position and texts.
| Prop | Type | |
|---|---|---|
texts | Record<string, string> | Per-language texts. They take precedence over config.consent.texts. |
policyPath | string | Link to the privacy policy, when you build the path yourself |
policyInline | string | The policy as HTML inside the banner. Use only your own content (for example, rendered CMS rich text), never user input. |
policy | ReactNode | The policy as React content. Takes precedence over policyInline. |
A footer link to reopen the settings:
const { reopen } = useConsent()
<button onClick={reopen}>Cookie settings</button>
useConsent() returns { enabled, policyVersion, decision, bannerVisible, save, acceptAll, rejectAll, reopen }.
dataLayer
useDataLayer() pushes GA4 e-commerce events to window.dataLayer for any tag manager. The cart, auth, checkout confirmation and product listing hooks push their events automatically (analytics.autoWire). For your own pages:
const dataLayer = useDataLayer()
useEffect(() => { if (product) dataLayer.viewItem(product) }, [product])
Wrap product grids in <DataLayerListProvider listId="…" listName="…"> so that select_item knows which list a click came from. To forward every push to Segment, Tealium or similar, pass onDataLayer to the provider. It receives { event, payload } before the push, can modify payload in place, and can return false to cancel the push.
<AvvyrAssistant />
The customer assistant chat, the same widget as the Nuxt layer's: streamed answers, product, order and tracking cards, return proposals, photos, handover to staff, and sign-in gating. Add it once to the layout:
import { AvvyrAssistant } from '@avvyr/storefront-next'
<AvvyrAssistant />
It renders nothing until the shop has the assistant enabled and the storefront key has the storefront:assistant scope, so it is safe to ship early. For how to enable it, the cards, photos, texts and the CSS variables, see the Nuxt assistant page. They work the same way here.
| Prop | Type | Default | |
|---|---|---|---|
locale | string | the market's | sv* gives Swedish texts, anything else English |
texts | Record<string, string> | Overrides config.assistant.texts | |
launcher | boolean | true | false hides the floating button; open it with useAssistant().open() |
position | 'bottom-right' | 'bottom-left' | 'bottom-right' | |
productSku, pageType, postalCode | string | Page context (see below) | |
suggestions | string[] | the shop's | Suggested questions for this page |
loginUrl | string | config.assistant.loginUrl | Where "Sign in" goes when only signed-in customers may chat |
siteOrigins | string[] | config.assistant.siteOrigins | Your other domains; links to them navigate in the app |
onOpen, onClose, onSend(text), onHandover, onProposal({ proposal, decision }), onSignIn | The Nuxt component's events | ||
renderSignIn({ close, texts }) | Replaces the "Sign in to chat" prompt (the Nuxt sign-in slot) |
Set the page context from the page that knows the product, since the chat usually sits in the layout:
'use client'
const assistant = useAssistant()
useEffect(() => {
assistant.setContext({ productSku: product.sku, pageType: 'product' })
return () => assistant.setContext(null)
}, [product.sku])
useAssistant() returns the chat's state as plain values (enabled, isOpen, status, messages, proposals, streaming, activity, error, signInRequired, attachments and more) and the actions open, close, toggle, send, stop, requestHandover, confirmProposal, rejectProposal, addAttachments, setContext, setLocale and reset. Use it for your own "Chat with us" button, or to build your own chat UI. AvvyrAssistantCard, AvvyrAssistantProposal and AvvyrAssistantText are exported too.
<AvvyrPaymentWidget />
The payment step. Give it the chosen payment method and it starts the session (checkout.init) and renders the provider's widget: the Kustom checkout snippet, Klarna Payments with an authorize button, a redirect button to a hosted payment page, or a button that places the order directly (invoice).
'use client'
import { AvvyrPaymentWidget } from '@avvyr/storefront-next'
import { useRouter } from 'next/navigation'
export function PaymentStep({ method, email }: { method: PaymentMethod; email: string }) {
const router = useRouter()
return (
<AvvyrPaymentWidget
paymentMethod={method}
email={email}
onCompleted={({ orderId }) => router.push(`/checkout/confirmation/${orderId}`)}
onError={(err) => console.error(err)}
/>
)
}
When the cart changes while the widget is open, the widget refreshes or restarts the session as needed. A changed price on the server is retried automatically and reported through onRepriced.
| Prop | |
|---|---|
paymentMethod | The PaymentMethod from usePaymentMethods() |
email, postalCode, customerType, companyId | Passed to checkout.init |
texts | placeOrder, pay, loading, failed, rejected |
onCompleted({ orderId, orderNumber }) | The order exists. Go to the confirmation with orderId. |
onRedirect(url) | Hosted payment page. Return false to navigate yourself. |
onError(error), onRepriced(info) | |
renderEmbedded({ session, kind }) | Stripe and Nexi embedded: mount the provider's widget with your own keys |
renderAuthorize, renderRedirect, renderDirect | Replace the default buttons |
ref | { suspend, resume, restart } |
<BreadcrumbJsonLd />
Structured data for breadcrumbs. It renders a <script type="application/ld+json"> and can be used from a Server Component:
import { BreadcrumbJsonLd } from '@avvyr/storefront-next'
<BreadcrumbJsonLd origin="https://store.se" items={[{ name: 'Home', to: '/' }, { name: 'Sofas', to: '/categories/sofas' }, { name: product.name }]} />
Pass origin so the script is part of the server HTML. Without it, the component reads window.location.origin and renders only after hydration. To build the list in server code, use buildBreadcrumbList from @avvyr/storefront-next/utils.
Pure helpers on the server
The package root is a client entry ('use client'), so a Server Component cannot call its functions. The helpers that server code also needs are exported from @avvyr/storefront-next/utils: buildCategoryTree, buildBreadcrumbList, marketPath, resolveMarket and the payment session helpers.