How analytics works

The SDK turns what happens on a page into tracking events: product_added_to_cart, checkout_started, checkout_completed. They are the SDK's own contract, named in the storefront style, and each carries the same object the SDK's events carry. It builds them from those events, so they come at the same moments, and hands each one to next.analytics.on and to the destinations that are on: GTM, GA4 and the Meta pixel are built in; any other vendor remaps them from a listener.

Tracking events through a funnel Tracking events and the engine events that send them, across a funnel. On every page: page_viewed on sdk.ready; product_added_to_cart, product_removed_from_cart and cart_updated on cart.calculated; promotion_viewed on promotion.viewed and promotion_selected on promotion.clicked. Landing: product_viewed for one package on the page, product_list_viewed for several, on sdk.ready. Checkout: checkout_started on checkout.started, checkout_contact_info_submitted on user.created, checkout_shipping_address_submitted on address.entered, checkout_shipping_method_submitted on shipping.selected, checkout_payment_info_submitted on checkout.submitted, checkout_completed on order.paid. Upsell: upsell_viewed on upsell.viewed, upsell_skipped on upsell.skipped, upsell_completed on order.loaded for the upsell accepted on the page before. Receipt: upsell_completed on order.loaded, and checkout_completed on order.loaded only when the checkout page was left before it was sent. Tracking events through a funnel Every page page_viewed ← sdk.ready product_added_to_cart · product_removed_from_cart · cart_updated ← cart.calculated promotion_viewed ← promotion.viewed · promotion_selected ← promotion.clicked Landing product_viewed ← sdk.ready product_list_viewed ← sdk.ready product_viewed when one package is on the page, product_list_viewed for more Checkout checkout_started ← checkout.started checkout_contact_info_submitted ← user.created checkout_shipping_address_submitted ← address.entered checkout_shipping_method_submitted ← shipping.selected checkout_payment_info_submitted ← checkout.submitted checkout_completed ← order.paid Upsell upsell_viewed ← upsell.viewed upsell_skipped ← upsell.skipped upsell_completed ← order.loaded upsell_completed is for the upsell accepted on the page before Receipt upsell_completed ← order.loaded checkout_completed ← order.loaded checkout_completed only when the checkout page was left before it was sent The checkout events come once per checkout, checkout_completed once per order, upsell_completed once per upsell.

When it loads

Analytics is its own 5 KB chunk, loaded when the browser is idle after sdk.ready, so it never holds up the first price. Events the page sent before it loaded are not lost: it reads them back in order, then follows new ones.

CODE
next.analytics.on('checkout_completed', (event) => {});
next.analytics.on('*', (event) => {});

A listener gets every event of its name this page has sent, then each new one, whether it was added before analytics loaded or long after. next.analytics.on returns a function that removes the listener.

The event

CODE
next.analytics.on('product_added_to_cart', (event) => {
  event.event_type; // 'product_added_to_cart'
  event.object; // 'cart_line'
  event.data; // the CartItem: package_id, product_title, sku, price, quantity, …
  event.context; // the page, campaign, session, attribution and browser
});

The envelope is the one next.on hands out, plus context:

FieldValue
event_idUnique, a UUID v4 as 32 hex digits; the order number on checkout_completed and ref_id-US1 on upsell_completed, so a tag can drop a repeat
event_typeThe name, such as 'checkout_completed'
objectWhat data is: cart_line, cart, checkout, order, upsell, promotion, product, products, page
dataThe event's object, on its page in the Analytics Reference: a Cart, a CartItem, the Checkout, the Order, …
created_atISO time
session_idThe campaign session, a UUID v4 as 32 hex digits
sdk_versionThe SDK version
contextThe same on every event, and what next.context() returns

context has page_location and page_referrer (without their query string), page_title, the campaign (id, name, currency, language), the campaign session_id, the attribution (UTM parameters, affiliate and click ids, landing_page, fbp, fbc) and the browser (user_agent, language, device_type). The shopper's details are not in context: the checkout_* events carry them in their Checkout and checkout_completed in its Order, as next.on does.

Once, even across pages

A funnel spans several pages, and a shopper can leave a page before analytics loaded on it. The SDK keeps track of what it already sent, so each event comes once:

EventSent
checkout_started, checkout_shipping_address_submitted, checkout_shipping_method_submitted, checkout_payment_info_submittedOnce per checkout. checkout_started not sent on the page that started the checkout comes on its next page
checkout_contact_info_submittedOnce, when the contact details save an Open Cart (user.created)
checkout_completedOnce per order in this browser: a reload of the receipt does not send it again
checkout_completed owedWhen the checkout page was left before analytics sent it, on the next page's order.loaded
upsell_completedOnce per accepted upsell, on the order.loaded of the page after the upsell
upsell_viewedOnce per upsell page

product_added_to_cart and product_removed_from_cart compare the priced cart with what earlier events in the tab already reported, on sdk.ready and after each price update, one event per line. An add on a page that was left before analytics loaded is still sent on the next page. A cart filled before the page loaded (a default bundle, forcePackageId, forceBundleId, a redirect from an add-to-cart button) sends product_added_to_cart at the cart price. A bundle change sends product_removed_from_cart for the old lines, at the price they were added, and product_added_to_cart for the new ones. first_in_session is true on the first product_added_to_cart of the tab, for tags that should send only one AddToCart.

Turning parts off

Analytics and GTM are on by default, GA4, the Meta pixel and RudderStack off; nextConfig.analytics turns analytics off or one destination on or off, and nothing for a destination that is off loads. See Destinations.

Debugging

?debug=1 opens the debug panel. Its Analytics tab lists each tracking event this page sent, newest first, with what it carried and ⚠ when something is off; click one for where it went (GTM, GA4, Meta, RudderStack: received, off, or missing its tag), the items and total it carried, and each payload. Page config in the panel's settings tries another nextConfig.analytics on the tab.

Every event, and every one the SDK chose not to send, is also a debug log line with its reason:

CODE
next.log.level = 'debug';
// analytics product_added_to_cart sent: Added since the last report: Starter ×1
// analytics checkout_started not sent: Already sent for this checkout
next.log.entries().filter((entry) => entry.message.startsWith('analytics '));