Next.js and custom storefronts
Add the JUST widget to a headless Shopify storefront built with Next.js or another custom frontend.
On a Next.js or custom storefront the Shopify globals are absent, so the host wires the
shop, locale, cart and consent explicitly. The integration stays client-side:
one script and one <just-ai-agent> web component, mounted once and kept
across route changes.
The steps below take the wires in order (the element, its attributes, your cart, the analytics hooks, your own entry points) and end on a complete example that carries all of them. Every attribute this page sets is documented in the attributes reference.
Prerequisites
- The JUST Shopify app installed and connected on your Shopify store.
- Your Shopify shop domain, for example
your-shop.myshopify.com.
Install
Load the widget script once and render the <just-ai-agent> element, for
example in your root layout:
// app/layout.tsx
<Script
type="module"
src="https://agent.getjust.eu/ai-commerce-agent-widget.js"
strategy="afterInteractive"
/>
<just-ai-agent shop="your-shop.myshopify.com" headless="true" />Replace your-shop.myshopify.com with your Shopify shop domain. The JUST
trigger bar now appears on every page.
Product-page detection is URL-based
The widget recognizes product pages by their /products/{handle} path only.
Storefronts with localized routes (/produits/…) lose product-page features:
the immersive view, suggested questions, and the product block.
Allow the widget through your CSP
If the storefront sends a Content Security Policy, allow
https://agent.getjust.eu in script-src, and allow both
https://agent.getjust.eu and https://agent-stream.getjust.eu in
connect-src. JUST's optional configured fonts also need
https://fonts.gstatic.com in font-src, and its Shadow DOM and Global CSS
require inline styles.
Do not replace the framework's existing sources when extending a directive. Keep its own scripts, connections and fonts, then append the JUST origins.
Strict CSP and Global JS
A shop-level Global JS override is injected as an inline script without your application's nonce. A nonce-only CSP blocks that optional override; the widget itself continues to work without Global JS.
Configure
Configure the widget with attributes on <just-ai-agent>. Two are required:
shop and headless="true". Set locale and country explicitly too,
because a headless storefront gives the widget no way to detect them:
<just-ai-agent
shop="your-shop.myshopify.com"
headless="true"
locale="en"
country="US"
/>country (ISO 3166-1 alpha-2) selects prices and currency; without it, prices
come from your shop's default market. The full attribute table is in the
attributes reference, and appearance
(theme, brand color, font, radius) is covered in
Customization.
Set locale explicitly
When the locale attribute is absent, the widget falls back to the page's
lang attribute and then to fr. A non-French storefront that omits locale
can ship a French widget.
CSS overrides
The widget renders inside a Shadow DOM, so your storefront CSS cannot reach its internals. Two CSS custom properties are supported on the host element:
just-ai-agent {
--ai-z-index: 100; /* render below your own drawers and modals */
--ai-bottom-position: 80px; /* clear a sticky bottom bar */
}Anything else inside the widget is internal and may change without notice. See the styling reference.
At boot the widget also appends one shop-scoped stylesheet to
document.head: <style data-just-ai="custom">, the Global CSS override
configured in the dashboard, removed when the shop has none. A shop can
carry a script twin too: <script data-just-ai="custom-js">, the
Global JS override, executed once per page load. If your frontend manages
<head> contents, leave both tags alone.
Connect your cart
On a headless storefront, your cart remains the source of truth. The widget
emits two DOM events on the <just-ai-agent> element; a small bridge
component answers them with your own cart implementation:
cart-add-requested: the widget asks your storefront to add a variant to the cart. Thedetailcarries{ variantId, quantity }:variantIdis a Shopify ProductVariant GID, usable as the Storefront APImerchandiseId, and a missingquantitycounts as 1.cart-clicked: the widget asks your storefront to open the cart experience. Close the widget in this handler, so the panel does not cover your cart drawer.
In the other direction, keep the cart-count attribute in sync with your
cart quantity: a non-negative integer, where a missing or invalid value hides
the badge.
The complete example below ships this bridge as one
drop-in component, JustBridge, with the analytics hooks of the next step
already wired.
Pages with a product block
If a page also renders a product
block, a second <just-ai-agent mode="block"> element, every selector in your bridge must target the
standalone instance: document.querySelector('just-ai-agent:not([mode="block"])').
Otherwise cart events land on the wrong element and add-to-cart dies
silently.
Set up analytics
The widget sends its analytics directly from the storefront. Some dashboard
metrics work out of the box; add-to-cart rates need the cart-added event,
and revenue attribution needs the cart-id attribute. Both are wired below.
Analytics explains what the dashboard measures and
why each wire matters.
Set tracking consent
The tracking-consent attribute controls analytics collection. When it is
"false", the widget sends no analytics and stores no visitor identifier. The
assistant itself is not affected and remains fully functional.
If your storefront requires opt-in consent (for example under GDPR), connect
the attribute to your consent platform and render "false" until the shopper
has opted in:
<just-ai-agent
shop="your-shop.myshopify.com"
headless="true"
tracking-consent={trackingConsent ? 'true' : 'false'}
/>When the attribute is omitted, analytics are enabled. The attribute can be updated at runtime; no page reload is required.
Report add-to-cart events
Report each successful add-to-cart by dispatching a cart-added event on the
<just-ai-agent> element:
export function reportCartAdded(variantId?: string, quantity?: number) {
widget()?.dispatchEvent(
new CustomEvent('cart-added', { detail: { variantId, quantity } }),
);
}Call it for every add-to-cart on the storefront (product page, quick-add, cart
drawer), not only the ones requested by the agent. Both payload fields are
optional; a missing quantity counts as 1.
Enable purchase attribution
Set the cart-id attribute to your cart identifier and update it when the
cart is created or replaced:
useEffect(() => {
if (cartId) {
widget()?.setAttribute('cart-id', cartId);
}
}, [cartId]);Pass cart-id verbatim
cart-id must be passed exactly as returned by the Storefront API, including
the ?key= parameter. A "cleaned" id silently breaks purchase attribution and
the cart confirmation card.
The widget stores its visitor identifier on the cart as a hidden cart
attribute (_ai_agent_visitor_id); existing cart attributes are preserved.
Purchases are then attributed to the visitor who made them automatically, and
to their conversation when they had one.
Open and close the widget
Open the widget from your own UI (a button, a search form, a campaign entry
point) by dispatching just:ai-agent:open on window, optionally with a
first message:
window.dispatchEvent(
new CustomEvent('just:ai-agent:open', {
detail: { message: 'Help me choose a gift', source: 'header_cta' },
}),
);Close it by dispatching just:ai-agent:close on window; the panel closes
back to the trigger bar. Both events are safe to dispatch before the widget
finishes loading; the most recent instruction wins.
window.dispatchEvent(new Event('just:ai-agent:close'));See entry points for the built-in ways to open the panel, and the events reference for the full event contract.
Recognize the logged-in customer
When a shopper is logged into a customer account, pass their email with the
customer-email attribute, set before the widget mounts, because it is read
once, at session creation:
<just-ai-agent
shop="your-shop.myshopify.com"
headless="true"
customer-email={customer?.email}
/>Omit it for a guest. With it set, guided flows skip asking for an email the
shopper already gave you. customer-id currently has no effect on a headless
storefront: the agent only recognizes a customer Shopify confirms, which needs a
Shopify theme.
Complete example
The two files below carry the full integration: element, cart bridge, and all analytics hooks.
// app/layout.tsx
import Script from 'next/script';
import { ReactNode } from 'react';
import { JustBridge } from '@/components/just-bridge';
export default function RootLayout({ children }: { children: ReactNode }) {
const trackingConsent = true; /* read the stored choice from your consent platform */
return (
<html lang="en">
<body>
{children}
<Script
type="module"
src="https://agent.getjust.eu/ai-commerce-agent-widget.js"
strategy="afterInteractive"
/>
<just-ai-agent
shop="your-shop.myshopify.com"
headless="true"
locale="en"
country="US"
tracking-consent={trackingConsent ? 'true' : 'false'}
/>
<JustBridge />
</body>
</html>
);
}// components/just-bridge.tsx
'use client';
import { useEffect } from 'react';
import { useCart } from '@/commerce/cart';
type CartAddRequestedDetail = { variantId: string; quantity: number };
const widget = () =>
document.querySelector('just-ai-agent:not([mode="block"])');
// Call after every successful add-to-cart on the storefront.
export function reportCartAdded(variantId?: string, quantity?: number) {
widget()?.dispatchEvent(
new CustomEvent('cart-added', { detail: { variantId, quantity } }),
);
}
export function JustBridge() {
const { cart, addLineItemToCart, openCart } = useCart();
const cartCount = cart?.totalQuantity ?? 0;
const cartId = cart?.id;
useEffect(() => {
const element = widget();
if (!element) return;
const onAddRequested = async (event: Event) => {
const { variantId, quantity } = (
event as CustomEvent<CartAddRequestedDetail>
).detail;
const updatedCart = await addLineItemToCart(variantId, quantity || 1);
// Set cart-id before reporting the add. On a first add the cart is
// created by the call above, so the effect below would set the
// attribute one render too late.
if (updatedCart?.id) {
widget()?.setAttribute('cart-id', updatedCart.id);
}
reportCartAdded(variantId, quantity || 1);
};
const onCartClicked = () => {
openCart();
window.dispatchEvent(new Event('just:ai-agent:close'));
};
element.addEventListener('cart-add-requested', onAddRequested);
element.addEventListener('cart-clicked', onCartClicked);
return () => {
element.removeEventListener('cart-add-requested', onAddRequested);
element.removeEventListener('cart-clicked', onCartClicked);
};
}, [addLineItemToCart, openCart]);
useEffect(() => {
widget()?.setAttribute('cart-count', String(cartCount));
}, [cartCount]);
useEffect(() => {
if (cartId) {
widget()?.setAttribute('cart-id', cartId);
}
}, [cartId]);
return null;
}Replace useCart, addLineItemToCart, and openCart with your storefront's
cart implementation, and call reportCartAdded from your own add-to-cart
actions. Have addLineItemToCart return the updated cart. The cartId effect
stays as it is, for carts created or replaced outside an add.
Testing
- The JUST trigger bar appears; open it, send a message, and confirm product-aware answers.
- Add a product from the chat: your bridge receives
cart-add-requested, the cart updates, the badge followscart-count, and the widget shows a confirmation card with the product and the cart total. Repeat on an empty cart to cover the first add. - The cart entry point in the widget opens your cart UI.
- A product-page add dispatches
cart-addedon<just-ai-agent>, and shows no card in the chat. - Once a cart exists, the
cart-idattribute is set on<just-ai-agent>. - With consent declined,
<just-ai-agent>renderstracking-consent="false"and no requests to/api/analytics/trackare sent; chat still works. - Start checkout and confirm the agent-added item is present.
The widget hides on session failure
When session creation fails (wrong shop domain, or a shop not enabled on JUST)
the widget hides entirely instead of showing an error. If the trigger bar
never appears, check the shop attribute first.
Analytics appear on your dashboard within about two hours of shopper activity.
Troubleshooting
Unexpected token 'export': confirm the script still hastype="module".- A CSP error names
agent.getjust.eu: add the origin to the directive named by the browser without removing the framework's existing sources. - The configured font falls back: allow
https://fonts.gstatic.cominfont-src. - The script loads but the widget disappears: check the console for
Shop not found, then verify the exactshopdomain and its activation in the same JUST environment. - TypeScript rejects
<just-ai-agent>: add it to React'sJSX.IntrinsicElementsin the project's declarations.