enhanced-analytics
A couple of convenient tools for populating dataLayer ecommerce event data or even more.
You can use them either on the Frontend or the Backend sides.
The Faacebook Pixel events can be configured on both sides, just to increase pixel's performance.
integrated services:
✅ Google Analytics & GA4 / Tag Manager (Browser Only)
✅ Klaviyo (Server+Browser) https://www.klaviyo.com/
- Install
npm i -S klaviyo-api@2.11
when this lib is being used at NodeJs - This will auto-install js when running it in a broswer.
✅ Facebook (Server+Browser)
- install
npm i -S facebook-nodejs-business-sdk@13.0.0
when running in NodeJs env
✅ FullStory (Broswer Only) https://www.fullstory.com/
- Install
npm i @fullstory/browser
when this lib is being used at NodeJs
1 Configure (UI Side)
import { useEffect } from 'react';
import { configureAnalytics } from 'enhanced-analytics';
import * as EATypes from 'enhanced-analytics';
const MyApp = () => {
const activeStore = {
name: 'My Store',
homepage: 'www.my-store.com',
localization: {
currency: 'USD',
},
};
useEffect(() => {
configureAnalytics({
affiliation: activeStore.name,
description: 'Your store description. This will appear in product feeds too',
absoluteURL: activeStore.homepage,
currency: activeStore.localization.currency,
debug:
integrations: {
klaviyo: {
enabled: true,
siteId: 'YOUR-SITE-ID',
},
ga: {
enabled: true,
trackId: 'GTM-XXXXXXX',
ga4: true,
},
fullstory: {
enabled: true,
orgId: 'YOUR-ORG-ID',
sdk: FullStory,
},
fb: {
enabled: true,
pixelId: 'YOUR-PIXEL-ID',
testCode: 'TEST61709',
},
},
resolvers: {
page(input) {
return {
id: '',
name: document.title,
path: window.location.pathname,
url: window.location.href,
title: document.title,
};
},
profile(input) {
const currUser = input || ;
return currUser?.userName && currUser?.email === 12
? {
email: currUser.email,
firstName: currUser.userName,
}
: null;
},
product: (p: any) => {
const res: EATypes.T_EA_DataProduct = {
id: p.id,
brand: p.seller,
category: p.category,
description: p.description,
isSale: !!p.promo,
price: p.price,
salePrice: p.price,
title: p.title,
sku: p.sku,
viewOrder: p.viewOrder,
};
return res;
},
basket: () => {
const diff = CartBuilderStore.getLastDiff();
const res: EATypes.TDataBasket = {
coupon: CartBuilderStore.getCouponCode(),
total: CartBuilderStore.getCartTotal(),
quantity: CartBuilderStore.getItemsCount(),
lastAdded: diff?.lastAddedItems.map(mapCartItemToAnalytics) || [],
lastRemoved:
diff?.lastRemovedItems.map(mapCartItemToAnalytics) || [],
products: CartBuilderStore.getItems().map(mapCartItemToAnalytics),
};
return res;
},
order: (o: any) => {
const res: EATypes.T_EA_DataOrder = {
id: o.id,
coupon: o.coupon,
dateCreated: o.dateCreated,
revenue: o.costsDetails.net,
status: o.status,
tax: o.taxValue,
payment: {
type: o.paymentType,
},
products: o.orderProducts,
quantity: o.orderTotal
customer: {
email: o.customerEmail,
firstName: o.customerFullName,
lastName: o.customerLastName,
phone: o.customerPhone,
address: {
street: o.customerAddress,
},
},
shipping: {
cost: o.costsDetails.feeValue,
name: o.deliveryMethod,
address: {
street: o.customerAddress,
},
},
};
return res;
},
},
});
}, []);
return <div>my app</div>;
};
1.2 Google Analytics
... somewhere in components:
import useAnalytics from 'enhanced-analytics';
const MyComponent = () => {
const analytics = useAnalytics();
useEffect(() => {
const myProductItems = [];
analytics
.withBasket()
.events.ga()
.getEECCheckoutList()
.when(() => true )
.push();
analytics
.withCatalog()
.events.ga()
.getEECProductsList()
.push();
analytics
.withCatalog()
.events.ga()
.getEECProductDetails()
.when(() => )
.push();
analytics
.withOrder()
.events.ga()
.getEECPurchased()
.when(() => !thisOrderWasSeen)
.push();
}, []);
return <></>;
};
1.2.Klaviyo UI / FullStory UI / Facebook Pixel
import useAnalytics from 'enhanced-analytics';
const MyComponent = () => {
const analytics = useAnalytics();
const order = useOrder();
useEffect(() => {
const evtPageView = analytics.withPage().events;
evtPageView.fullstory().trackPageView();
evtPageView.klaviyo().trackPageView();
}, []);
useEffect(() => {
const evtPageView = analytics.withPage().events;
evtProfile.fullstory().trackIdentify();
evtProfile.klaviyo().trackIdentify();
evtPageView.fb().trackPageView();
}, []);
useEffect(() => {
const evtOrder = analytics.withOrder(order).events;
const evtProfile = analytics.withProfile({
userName: order.customerFullName,
phone: order.customerPhone,
}).events;
const evtCustom = analytics.withMisc('orderSeen', {
orderId: order.id,
orderDate: moment(order.dateCreated).format(),
orderTotal: order.total,
}).events;
evtOrder.ga().getEECPurchased().push();
evtProfile.fullstory().trackIdentify();
evtProfile.klaviyo().trackIdentify();
evtPageView.fb().trackIdentify();
evtCustom.fullstory().trackCustom();
evtCustom.klaviyo().trackCustom();
evtPageView.fb().trackCustom();
}, []);
};
2 Configure (Backend Side)
Simple snippet with Express.Js as middleware:
app.use(
analyticsMiddleware({
absoluteURL: 'https://www.your-domain.lviv.ua/',
serverAnalytics: {
testing: false,
klaviyo: {
enabled: true,
token: 'pk_token-goes-here',
sdk: require('klaviyo-api'),
},
userIdentification: {
reqBodyKey: 'customerPhone',
},
},
resolvers: (req) => ({
order(evtPayload: any) {
const order = evtPayload.order;
const orderProducts = evtPayload.products;
return {
id: order.id,
revenue: order.total,
tax: 0,
quantity: order.features.length,
coupon: order.coupon,
products: [],
dateCreated: order.dateCreated,
status: order.status,
shipping: {
cost: order.deliveryFee,
name: order.deliveryMethod,
address: {
street: order.customerAddress,
},
},
customer: {
firstName: order.customerFullName,
email: `john.smith@test.com`,
},
payment: {
type: order.paymentType,
},
url: `${req.protocol}://${req.hostname}/order/success/${order.externalId}`,
};
},
profile() {
return app.locals.customer.customerPhone
? {
email: `john.smith@test.com`,
firstName: app.locals.customer.customerFullName,
phoneNumber: '5551234567',
address: {
country: 'United States',
city: 'Boston',
postcode: '02110',
region: 'MA',
countryCode: 'UA',
street: app.locals.customer.customerAddress,
},
}
: null;
},
eventUUID() {
return app.locals.evtUuid;
},
product(input: TDataList<IDataProduct>) {
const l = input.items.map((prodItem) => {
return {
id: prodItem.id,
title: prodItem.title,
description: prodItem.description,
price: prodItem.price,
salePrice: prodItem.price,
isSale: false,
brand: prodItem.seller,
category: prodItem.categoryName,
sku: prodItem.sku,
list: 'main',
url: `${req.protocol}://${req.hostname}/product/${prodItem.id}/${prodItem.sku}`,
imageUrl: prodItem.imageUrl,
};
});
return l;
},
page() {
return {
id: req.baseUrl,
name: req.path.split('/')[0],
path: req.path,
title: 'Main Page',
url: `${req.protocol}://${req.hostname}${req.originalUrl}`,
};
},
session() {
return {
agent: req.headers['user-agent'],
fbp: req.cookies['_fbp'],
ip: req.ip,
};
},
}),
})
);
Another configuration for NextJs:
const getServerEA = (req: GetServerSidePropsContext['req']) => {
const ea = useAnalytics({
absoluteURL: "https://my-store.com/",
affiliation: "My Store",
currency: "USD",
integrations: {
testing: true,
fb: {
enabled: true,
pixelId: "PIXEL-ID",
token: "PIXEL-TOKEN",
sdk: bizSdk,
testCode: "TESTxxxxx",
},
},
resolvers: {
eventUUID() {
return Date.now().toString(32);
},
session() {
return {
agent: req.headers["user-agent"],
fbp: req.cookies["_fbp"],
ip: req.socket.remoteAddress,
};
},
profile() {
const user =
return user
? {
email: user.email,
firstName: user.username,
}
: null;
},
basket() {
return {
total: 0,
coupon: null,
quantity: 0,
lastAdded: [],
lastRemoved: [],
products: [],
};
},
},
});
return ea;
}
2.1 Use It
2.2.Klaviyo API
Track new order:
import useAnalytics from 'enhanced-analytics';
const evtPayload = { order, products };
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();
Begin checkout:
import useAnalytics from 'enhanced-analytics';
const evtPayload = { order, products };
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackInitiateCheckout();
All methods:
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackIdentify();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackProductAddToCart();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackProductRemoveFromCart();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProductItemView();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackProductsItemView();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackSearch();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackPageView();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackInitiateCheckout();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackNewProfile();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackProfileResetPassword();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogIn();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogOut();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackProfileSubscribeNL();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackTransactionRefund();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackTransactionCancel();
await useAnalytics()
.withOrder(evtPayload)
.s2s.klaviyo()
.trackTransactionFulfill();
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackCustom();
2.2.FB Pixel (Server+UI)
This examples shows how to send server-side (NextJs) fb events and then re-process them from the UI.
import { GetServerSideProps } from "next";
import { useEffect } from "react";
import {
EA_FB_Server_RePublish_Events,
TFbNormalizedEventPayload,
} from "enhanced-analytics/apiTracker/facebook";
import useAnalytics, { configureAnalytics } from "enhanced-analytics";
import * as bizSdk from "facebook-nodejs-business-sdk";
import { GetServerSidePropsContext } from "next";
interface IShopProductResponse {
eaFbEvents: any;
}
export default function AnyProductPage({
eaFbEvents,
}: IShopProductResponse) {
useEffect(() => {
analytics
.withPage({
name: props.title,
path: window.location.pathname,
})
.events.fb()
.trackPageView();
}, []);
return (
<div>
<span>testing fb events</span>
{* The component from EA, which is digesting handed server response *}
<EA_FB_Server_RePublish_Events serverPayloads={props.eaFbEvents} />
</div>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, }) => {
const product = fetch();
const ea = getServerEA(req);
const fbResp = await ea
.withCatalog([
{
id: product.id,
title: product.title,
description: product.description,
salePrice: roundToTwo(product.salePrice),
price: product.salePrice || product.price,
isSale: roundToTwo(product.salePrice) > 0,
brand: product.brand,
category: product.category,
color: product.color,
sku: product.sku,
imageUrl: product.images.length > 0 ? product.images[0] : void 0,
url: `https://my-store.com/any-product/${product.shortId}/${product.slug}`,
},
])
.s2s.fb()
.trackProductItemView();
return {
props: {
eaFbEvents: fbResp[0].value.payload
},
};
};
3 useAnalytics top methods
const ea = useAnalytics();
ea.identify(user: T_EA_DataProfile);
ea.withMisc(name: string, attributes?: Record<string, any>);
ea.withPage(payload: T_EA_DataPage | Record<string, any> | null = null);
ea.withProfile(payload: T_EA_DataProfile | Record<string, any> | null = null);
ea.withCatalog(payload: (T_EA_DataProduct | Record<string, any>)[] | null = null);
ea.withBasket(payload: T_EA_DataBasket | Record<string, any> | null = null);
ea.withOrder(payload: T_EA_DataOrder | Record<string, any> | null = null);