@jwiedeman/gtm-kit-next
v1.1.6
Published
Next.js App Router helpers for GTM Kit - Google Tag Manager integration with Server Components support.
Maintainers
Readme
@jwiedeman/gtm-kit-next
Next.js App Router components for Google Tag Manager. Server components ready.
The Next.js adapter for GTM Kit - provides server components and route tracking hooks.
Installation
npm install @jwiedeman/gtm-kit @jwiedeman/gtm-kit-nextyarn add @jwiedeman/gtm-kit @jwiedeman/gtm-kit-nextpnpm add @jwiedeman/gtm-kit @jwiedeman/gtm-kit-nextQuick Start
Step 1: Add to Layout
// app/layout.tsx
import { GtmHeadScript, GtmNoScript } from '@jwiedeman/gtm-kit-next';
export default function RootLayout({ children }) {
return (
<html>
<head>
<GtmHeadScript containers="GTM-XXXXXX" />
</head>
<body>
<GtmNoScript containers="GTM-XXXXXX" />
{children}
</body>
</html>
);
}Step 2: Create Client Provider
// app/providers/gtm.tsx
'use client';
import { createGtmClient } from '@jwiedeman/gtm-kit';
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
client.init();
export function GtmProvider({ children }) {
useTrackPageViews({ client }); // Auto-tracks route changes
return children;
}Step 3: Push Events
'use client';
import { pushEvent } from '@jwiedeman/gtm-kit';
// In any client component
function BuyButton({ client }) {
return <button onClick={() => pushEvent(client, 'purchase', { value: 49.99 })}>Buy Now</button>;
}Features
| Feature | Description |
| ---------------------- | ------------------------------------------------------- |
| Server Components | GtmHeadScript and GtmNoScript are server components |
| App Router | Built for Next.js 13+ App Router |
| Auto Page Tracking | useTrackPageViews hook for route changes |
| CSP Support | Nonce support for Content Security Policy |
| TypeScript | Full type definitions included |
| Lightweight | Only what you need for Next.js |
Server Components
<GtmHeadScript />
Renders the GTM script tag. Place in your <head>.
import { GtmHeadScript } from '@jwiedeman/gtm-kit-next';
<GtmHeadScript
containers="GTM-XXXXXX"
scriptAttributes={{ nonce: 'your-csp-nonce' }} // Optional
/>;<GtmNoScript />
Renders the noscript fallback iframe. Place at the start of <body>.
import { GtmNoScript } from '@jwiedeman/gtm-kit-next';
<GtmNoScript containers="GTM-XXXXXX" />;Client Hooks
useTrackPageViews()
Automatically tracks page views on route changes.
'use client';
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
export function GtmProvider({ children, client }) {
useTrackPageViews({ client });
return children;
}Full Setup Example
1. Root Layout
// app/layout.tsx
import { GtmHeadScript, GtmNoScript } from '@jwiedeman/gtm-kit-next';
import { GtmProvider } from './providers/gtm';
export default function RootLayout({ children }) {
return (
<html>
<head>
<GtmHeadScript containers="GTM-XXXXXX" />
</head>
<body>
<GtmNoScript containers="GTM-XXXXXX" />
<GtmProvider>{children}</GtmProvider>
</body>
</html>
);
}2. GTM Provider
// app/providers/gtm.tsx
'use client';
import { createGtmClient } from '@jwiedeman/gtm-kit';
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
import { createContext, useContext } from 'react';
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
client.init();
const GtmContext = createContext(client);
export function GtmProvider({ children }) {
useTrackPageViews({ client });
return <GtmContext.Provider value={client}>{children}</GtmContext.Provider>;
}
export function useGtmClient() {
return useContext(GtmContext);
}3. Use in Components
// app/components/BuyButton.tsx
'use client';
import { pushEvent } from '@jwiedeman/gtm-kit';
import { useGtmClient } from '../providers/gtm';
export function BuyButton() {
const client = useGtmClient();
return <button onClick={() => pushEvent(client, 'purchase', { value: 49.99 })}>Buy Now</button>;
}Consent Mode v2 (GDPR)
// app/providers/gtm.tsx
'use client';
import { createGtmClient, consentPresets } from '@jwiedeman/gtm-kit';
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
// Set consent defaults BEFORE init
client.setConsentDefaults(consentPresets.eeaDefault, { region: ['EEA'] });
client.init();
export function GtmProvider({ children }) {
useTrackPageViews({ client });
return children;
}
// Export for consent updates
export { client };// app/components/CookieBanner.tsx
'use client';
import { client } from '../providers/gtm';
import { consentPresets } from '@jwiedeman/gtm-kit';
export function CookieBanner() {
// Accept all tracking
const acceptAll = () => client.updateConsent(consentPresets.allGranted);
// Reject all tracking
const rejectAll = () => client.updateConsent(consentPresets.eeaDefault);
// Analytics only (mixed consent)
const analyticsOnly = () => client.updateConsent(consentPresets.analyticsOnly);
// Partial update - only change specific categories
const customChoice = () =>
client.updateConsent({
analytics_storage: 'granted',
ad_storage: 'denied'
});
return (
<div>
<button onClick={acceptAll}>Accept All</button>
<button onClick={rejectAll}>Reject All</button>
<button onClick={analyticsOnly}>Analytics Only</button>
</div>
);
}Granular Updates - Update individual categories without affecting others:
// User later changes ad preferences from settings page
client.updateConsent({ ad_storage: 'granted', ad_user_data: 'granted' });
// analytics_storage and ad_personalization remain unchangedCSP (Content Security Policy)
For strict CSP configurations, pass a nonce:
// app/layout.tsx
import { headers } from 'next/headers';
import { GtmHeadScript, GtmNoScript } from '@jwiedeman/gtm-kit-next';
export default function RootLayout({ children }) {
const nonce = headers().get('x-nonce') || '';
return (
<html>
<head>
<GtmHeadScript containers="GTM-XXXXXX" scriptAttributes={{ nonce }} />
</head>
<body>
<GtmNoScript containers="GTM-XXXXXX" />
{children}
</body>
</html>
);
}Multiple Containers
<GtmHeadScript
containers={[{ id: 'GTM-MAIN' }, { id: 'GTM-ADS', queryParams: { gtm_auth: 'abc', gtm_preview: 'env-1' } }]}
/>Pages Router (Legacy)
For Next.js Pages Router, use @jwiedeman/gtm-kit-react instead:
// pages/_app.tsx
import { GtmProvider } from '@jwiedeman/gtm-kit-react';
export default function App({ Component, pageProps }) {
return (
<GtmProvider config={{ containers: 'GTM-XXXXXX' }}>
<Component {...pageProps} />
</GtmProvider>
);
}Requirements
- Next.js 13.4+ (App Router)
- React 18+
@jwiedeman/gtm-kit(peer dependency)
Related Packages
- Core: @jwiedeman/gtm-kit (required)
- React: @jwiedeman/gtm-kit-react
Support
Have a question, found a bug, or need help?
Open an issue on GitHub — we're actively maintaining this project and respond quickly.
License
MIT
