nextjs-trackkit
v1.1.1
Published
Simple GTM dataLayer tracking for Next.js components using typed helpers and data attributes.
Maintainers
Readme
nextjs-trackkit
Simple GTM tracking for Next.js projects.
This package helps teams track component views, clicks, controls, videos, and
completion events with a consistent window.dataLayer schema. For most
components, developers can either use ready-made React helpers or add
data-track-* attributes.
Install
npm install nextjs-trackkitChoose Your Tracking Method
TrackKit supports two easy ways to track events. Both send the same consistent
window.dataLayer event schema, so you can choose the style that fits your app.
Way 1: React Components And Hooks
Best for most Next.js apps and design systems.
- Define tracking context once with
TrackKitSection - Use
TrackKitLinkandTrackKitButtonfor reusable CTAs - Use
useTrackKitCta()when you already have your own button, link, or card component - Best when you want less repeated
data-track-*markup
Way 2: Data Attributes
Best when you want tracking directly in markup.
- Add
data-track-viewfor viewport events - Add
data-track-clickfor click and control events - Put shared content details on a parent section with
data-track-context - Best when you want tracking without importing React tracking components
Manual helper functions are also available for advanced cases such as videos, completion events, and fully custom events.
Next.js Setup
Wrap your app in the client provider.
// app/providers.tsx
"use client";
import { GtmTrackingProvider } from "nextjs-trackkit/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<GtmTrackingProvider gtmId={process.env.NEXT_PUBLIC_GTM_ID}>
{children}
</GtmTrackingProvider>
);
}Use it in your root layout.
// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}The provider:
- creates
window.dataLayer - sets default GTM consent to denied
- loads GTM when
gtmIdis provided - enables data-attribute click and viewport tracking
Way 1: React Components And Hooks
Use the React API when you want less boilerplate in reusable components. Define shared content context once at the section level, then use tracked buttons and links inside it.
"use client";
import {
TrackKitButton,
TrackKitLink,
TrackKitSection,
} from "nextjs-trackkit/react";
export function Hero() {
return (
<TrackKitSection
view
content={{
name: "Hero",
type: "Content",
variant: "Primary",
media: "Image",
}}
customParameters={{
campaign_id: "launch",
}}
>
<h1>Modern tracking for Next.js</h1>
<TrackKitLink href="/setup" cta={{ type: "Button" }}>
Start setup
</TrackKitLink>
<TrackKitButton cta={{ type: "Button" }} eventType="control">
Open preview
</TrackKitButton>
</TrackKitSection>
);
}TrackKitSection provides inherited content, customParameters, and
details to child CTAs. The view prop adds the same viewport tracking
attributes used by the declarative API, so the existing observer fires
content_in_viewport when the section enters the viewport.
TrackKitLink pushes content_cta_click. TrackKitButton pushes
content_control_interaction by default, and can push content_cta_click with
eventType="cta".
Component-Level CTA Hook
Use useTrackKitCta() when your design system already owns the rendered button,
card, or link element.
"use client";
import { useTrackKitCta } from "nextjs-trackkit/react";
export function ProductCardLink() {
const { track, trackingProps } = useTrackKitCta({
cta: {
type: "Card",
text: "View product",
},
href: "/products/trackkit",
});
return (
<a
href="/products/trackkit"
{...trackingProps}
onClick={() => track("View product")}
>
View product
</a>
);
}The hook reads inherited context from TrackKitSection or
TrackKitProvider.defaults. It returns:
track(label)to push the event manuallytrackingPropswith usefuldata-track-*metadata for inspection or optional declarative fallback
Set enableDeclarativeFallback: true only when you want the returned props to
include data-track-click. Do not call track() and enable declarative fallback
for the same interaction unless you intentionally want two events.
Provider Defaults
Use provider defaults for values shared across the whole app.
<TrackKitProvider
gtmId={process.env.NEXT_PUBLIC_GTM_ID}
defaults={{
content: {
type: "Content",
media: "Text",
},
customParameters: {
site_area: "marketing",
},
}}
>
{children}
</TrackKitProvider>Way 2: Data Attributes
The original data-track-* API is still fully supported. Use it when you want
tracking without importing React components into each UI file.
Track Component Views With Data Attributes
Add data-track-view to the component wrapper. The event fires once by default
when the element enters the viewport.
<section
data-track-view
data-track-content-name="Hero"
data-track-content-type="Content"
data-track-content-variant="Primary"
data-track-content-media="Image"
>
<h1>Welcome</h1>
</section>This pushes:
{
event: "content_in_viewport",
parameters: {
content: {
name: "Hero",
type: "Content",
variant: "Primary",
media: "Image",
page: "/current-page"
}
}
}Track Clicks With Data Attributes
Add data-track-click to links, buttons, or CTAs.
<a
href="/products"
data-track-click
data-track-label="Explore products"
data-track-cta-type="Button"
>
Explore products
</a>This pushes content_cta_click.
Inherit Context From Parent Data Attributes
Put shared tracking values on the organism or section. Child atoms only need the click trigger and label.
<section
data-track-context
data-track-view
data-track-content-name="Bento Grid"
data-track-content-type="Content"
data-track-content-variant="Featured"
data-track-content-media="Image"
data-track-position="2"
data-track-cardinality="4"
>
<a
href="/learn-more"
data-track-click
data-track-label="Learn more"
data-track-cta-type="Button"
>
Learn more
</a>
</section>Track Controls With Data Attributes
Use data-track-event-type="control" for tabs, accordions, filters, carousel
controls, toggles, radio buttons, and similar UI controls.
<button
data-track-click
data-track-event-type="control"
data-track-cta-type="Button"
data-track-label="Overview"
data-track-position="1"
>
Overview
</button>This pushes content_control_interaction.
Control events use this action shape:
{
event: "content_control_interaction",
parameters: {
content: {
name: "Tracking Console",
type: "Content",
variant: "Diagnostics",
media: "Text",
page: "/"
},
action: {
type: "Button",
text: "Overview"
}
}
}action.text is always the visible button/link/card label, or
data-track-label when provided. action.type should describe the CTA surface:
Button, Text, or Card.
action.target is only sent for download CTAs. Its value is always Download,
and download CTAs also force content.media to File.
<a
href="/files/annual-report.pdf"
download
data-track-click
data-track-event-type="control"
data-track-cta-type="Button"
data-track-cta-target="Download"
data-track-label="Download annual report"
>
Download annual report
</a>This pushes:
{
event: "content_control_interaction",
parameters: {
content: {
name: "Tracking Console",
type: "Content",
variant: "Diagnostics",
media: "File",
page: "/"
},
action: {
type: "Button",
text: "Download annual report",
target: "Download"
}
}
}Data Attribute Reference
Use these on wrapper components:
data-track-contextdata-track-content-namedata-track-content-typedata-track-content-variantdata-track-content-layoutdata-track-content-labeldata-track-content-mediadata-track-content-pagedata-track-content-regiondata-track-content-displaydata-track-positiondata-track-cardinality
Use these on tracked elements:
data-track-viewdata-track-clickdata-track-event-type="cta"data-track-event-type="control"data-track-labeldata-track-destinationdata-track-target="_self"data-track-target="_blank"data-track-once="true"data-track-once="false"data-track-threshold="0.3"
CTA-specific attributes:
data-track-cta-typedata-track-cta-mediadata-track-cta-target="Download"
For content_control_interaction, use data-track-cta-type for
action.type and data-track-label for action.text.
Advanced: Manual Tracking
Use manual helpers for components that already have custom logic, video events, completion events, or custom event needs that do not map to a simple CTA or viewport interaction.
import {
trackActionCompletion,
trackContentControlInteraction,
trackContentCtaClick,
trackContentInViewport,
trackCustomEvent,
trackVideoEngagement,
trackVideoProgress,
} from "nextjs-trackkit";
trackContentCtaClick({
content: {
name: "Hero",
type: "Content",
variant: "Primary",
media: "Image",
},
cta: {
type: "Button",
text: "Explore products",
},
navigateToUrl: "/products",
});Available event helpers:
trackContentInViewport()pushescontent_in_viewporttrackContentCtaClick()pushescontent_cta_clicktrackContentControlInteraction()pushescontent_control_interactiontrackVideoEngagement()pushesvideo_engagementtrackVideoProgress()pushesvideo_progresstrackActionCompletion()pushesaction_completiontrackCustomEvent()pushes any custom event name and parameters
Custom Events And Parameters
Use trackCustomEvent() when you need an event outside the predefined schema.
The package sends your event name and parameters directly to window.dataLayer.
import { trackCustomEvent } from "nextjs-trackkit";
trackCustomEvent({
event: "signup_step_completed",
parameters: {
flow_name: "Onboarding",
step_name: "Choose plan",
step_number: 2,
plan_type: "Starter",
},
});This pushes:
{
event: "signup_step_completed",
parameters: {
flow_name: "Onboarding",
step_name: "Choose plan",
step_number: 2,
plan_type: "Starter"
}
}You can also add extra parameters to predefined event helpers with
customParameters. Custom parameters are merged into the top-level
parameters object. Predefined fields such as content, cta, action,
details, video, and response keep priority if a custom key overlaps.
trackContentCtaClick({
content: {
name: "Pricing Hero",
type: "Marketing Section",
variant: "Primary",
media: "Text",
},
cta: {
type: "Button",
text: "Start free trial",
},
navigateToUrl: "/signup",
customParameters: {
campaign_id: "summer_launch",
audience_segment: "developers",
experiment_id: "pricing_cta_v2",
},
});This pushes content_cta_click with the normal content and cta objects,
plus:
{
campaign_id: "summer_launch",
audience_segment: "developers",
experiment_id: "pricing_cta_v2"
}GTM Setup
Create Custom Event triggers for:
content_in_viewportcontent_cta_clickcontent_control_interactionvideo_engagementvideo_progressaction_completion- any custom event names you send with
trackCustomEvent()
Create Data Layer Variables for the fields you need:
parameters.content.nameparameters.content.typeparameters.content.variantparameters.content.mediaparameters.content.pageparameters.content.regionparameters.cta.typeparameters.cta.textparameters.cta.urlparameters.cta.destinationparameters.action.typeparameters.action.textparameters.action.targetparameters.action.positionparameters.details.positionparameters.details.cardinalityparameters.video.actionparameters.video.titleparameters.videoProgress.percentageparameters.response.status- any custom parameter keys you send, such as
parameters.campaign_id
Map those variables into GA4 event parameters on the matching GTM tags.
Consent
By default the provider sets GTM consent to denied. After your consent manager gets approval, update consent:
import { updateGtmConsent } from "nextjs-trackkit";
updateGtmConsent({
analytics_storage: "granted",
ad_storage: "granted",
});Avoid Duplicate Events
For one component interaction, use one tracking path only:
- React components/hooks with manual
track()calls - Data attributes with automatic delegated tracking
- Manual helper functions
Do not combine multiple paths for the same click or view unless you intentionally want more than one event.
Made with ❤️ for Next.js developers.
👤 Author
Neetesh Keshari [AKQA]
- GitHub: https://github.com/neeteshkeshari
- Website: https://linktr.ee/neeteshkeshari
- LinkedIn: https://www.linkedin.com/in/neeteshkeshari/
