Hooks
Every hook must be used inside <StorefrontProvider> in a client component ('use client'). The hooks have the same names as the Nuxt composables. They return plain values instead of refs.
'use client'
import { useCart } from '@avvyr/storefront-next'
export function AddToCart({ sku }: { sku: string }) {
const { addItem, loading } = useCart()
return <button disabled={loading} onClick={() => addItem({ sku, quantity: 1 })}>Add to cart</button>
}
Fetch in the server, hydrate in the client
In the App Router, a page usually fetches its data in a Server Component with storefront.client(). Pass the result as initialData when a client component needs it to stay live. The hook then renders the server's data without a second request:
// app/products/[handle]/page.tsx (Server Component)
const product = await (await storefront.client()).products.getByHandle(handle)
return <ProductView handle={handle} initialProduct={product} />
// ProductView.tsx ('use client')
const { data: product } = useProductByHandle(handle, { initialData: initialProduct })
Query hooks return { data, error, loading, refresh }. Hooks that share a key share both the request and the result. Pass null instead of a parameter to pause a hook.
useCart()
The cart is shared across the app. Its id and token are stored in cookies, so the server renders the cart on the next request.
const {
cart, // StorefrontCart | null
loading, // boolean, stays true across multi-step updates
itemCount, // number of items (charge lines excluded)
create, fetch,
addItem, addConfiguredItems, updateConfiguredItems, removeConfiguredGroup,
removeItem, updateItemQuantity,
setCustomerType, setCustomer, setAddress, setShipping,
applyCode, removeCode,
complete, // needs the private key: call it from a Server Action instead
clear,
} = useCart()
addItem creates a cart when there is none. Cart mutations push GA4 add_to_cart and remove_from_cart events to the dataLayer, and send add_to_cart to Avvyr tracking when the visitor has consented.
useAuth()
const {
customer, // CustomerProfile | null
isAuthenticated, // boolean, from the session cookie (same on server and client)
loading,
login, register,
sendMagicLink, verifyMagicLink, verifyMagicLinkCode,
acceptInvite,
refresh, logout,
fetchMe, updateMe, setPassword,
} = useAuth()
customer is null until you call fetchMe(), unless the layout called storefront.initialState({ customer: true }). Call verifyMagicLink from a button click, never on mount: email scanners open links and would use up the one-time token. Signing in or out reloads the page (see How sessions work).
To protect pages, list them in auth.protectedPaths in the config (checked by the proxy) or call await storefront.requireSession() in the page.
useMarket()
const {
market, marketId, id, name, locale, i18nLocale, currency,
markets, strategy,
path, // (p) => p, with the market prefix under the path strategy
switchMarket, // (id) => void
} = useMarket()
Catalog
| Hook | Returns |
|---|---|
useProductSearch(params, options?) | ProductSearchResponse. Keeps the previous result while the next one loads. |
useProductByHandle(handle, options?) | StorefrontProduct |
useProductBySku(sku, options?) | StorefrontProduct |
useProductSuggest(params, options?) | Typeahead suggestions. Pauses while q is empty. |
useRelatedProducts(sku, { attributes?, variants? }, options?) | RelatedProductGroup[] |
useProductsByRule(ruleId, context?, options?) | Products from a recommendation rule |
useCategories(options?) | StorefrontCategory[] from the server's category cache |
useCategoryByHandle(handle, options?) | StorefrontCategory |
useCategoryProducts(params, options?) | Products in a category |
useReviews(params) / useReviewStatistics() | Reviews and aggregates |
useCampaigns(locale?) / useCampaignByCode(code, locale?) | Active campaigns |
useShippingOptions(params) / useProductShipping(sku, { postalCode }) | Shipping options |
useProductListing(options)
A paginated product listing for category and search pages. "Load more" appends pages, and ?page= is kept in the URL so a reload or the back button returns to the same position. Facets and sort are included.
// The Server Component fetches page 1 and passes it on, so the first render needs no client request.
const listing = useProductListing({
params: { categoryId, filters },
limit: 24,
analytics: { listId: `category-${handle}`, listName: name },
initialData: firstPage,
})
const { products, facets, total, sort, setSort, hasMore, loadMore, hasPrevious, loadPrevious, loading } = listing
The hook reads useSearchParams, so on a statically rendered page you must wrap the component in <Suspense>. It updates the URL with history.replaceState, so loading another page does not re-render the page's Server Component. It pushes GA4 view_item_list for every page it loads.
Navigation and history
const { tree, roots, loading } = useCategoryNav({ initialData: categories }) // nested menu from useCategories()
const { crumbs } = useProductBreadcrumb(product) // category trail for a product
const { skus, track, clear, getProducts } = useRecentlyViewed() // localStorage, empty during SSR
B2B: company and saved lists
const {
company, members, invites, companies, activeCompany, setActiveCompany,
fetchCompany, fetchMembers, fetchInvites,
inviteMember, revokeInvite, updateMemberRole, removeMember,
updateAddress, addShippingAddress, updateShippingAddress, removeShippingAddress,
} = useCompany()
const {
lists, favorites, membership, loaded,
fetchLists, getList, createList, updateList, shareList, deleteList, duplicateList,
addItems, updateItem, removeItem,
isSaved, toggleFavorite, addToCart,
} = useSavedLists()
Checkout and orders
const { init, authorize, loading } = useCheckout()
const { data: confirmation } = useCheckoutConfirmation(reference) // pushes GA4 purchase once per order
const { methods, fetchPaymentMethods } = usePaymentMethods()
const { validate, create } = useReturns()
const { data: orders } = useCustomerOrders({ page: 1 }) // pauses until signed in
const { data: order } = useCustomerOrder(orderNumber)
const { data: publicOrder } = usePublicOrder(token)
useMarketDelivery() returns the market's delivery rules: { market, deliveryCountries, deliversTo(countryCode), loaded }. usePriceVisibility() returns { pricesRequireLogin, canSeePrices } for markets that only show prices to signed-in customers.
Customer type
const { customerType, showIncVat, setCustomerType, toggleCustomerType } = useCustomerType()
The value is stored in a cookie, so prices render with the right VAT basis on the server.
Tracking and consent
useTracking() sends Avvyr behavioural events: { track, flush, grantConsent, revokeConsent, consentGranted, identity }. The provider sends page_view on every navigation. Nothing is sent and no identity cookies are set until the visitor consents (tracking.requireConsent, default true).
useConsent() and useDataLayer() are described with <AvvyrCookieConsent /> in Components.
Low-level
| Hook | Use |
|---|---|
useStorefrontClient() | The browser SDK client (public key) |
useStorefrontConfig() / useStorefrontEnv() | The resolved config / public env values |
useStorefrontQuery(key, fetcher, options) | Your own cached query: useStorefrontQuery('brands', (c) => c.products.search({ … })) |
useSharedState(key, init) | App-wide state shared by key, the equivalent of Nuxt's useState |
useInvalidateQueries() | invalidate(prefix?) marks cached queries stale |