This guide covers installing Walityk in a Next.js application, App Router (13+) or Pages Router. No package to install: Walityk integrates with a snippet and the next/script component. Plan for 10 minutes.
1. Create your site in the dashboard
- Sign in to the Walityk dashboard.
- Sites → New site: enter your domain (e.g.
app.example.com) and pick the Custom (HTML/JS) platform. - Click Create site. The site page’s Installation tab shows your snippet with your site ID already filled in.
The declared domain is what authorizes your requests (CORS): the exact domain and its subdomains are accepted automatically. If you track several distinct domains, create one site per domain.
2. Add the tag — App Router
In app/layout.tsx, add the snippet via next/script:
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script id="walityk" strategy="afterInteractive">
{`(function(s,i,e){
s.__tftq=s.__tftq||[];
s.__tftq.push(['init',{site_id:i,endpoint:e+'/collect'}]);
var t=document.createElement('script');
t.async=true;
t.src=e+'/tag.js';
document.head.appendChild(t);
})(window,'YOUR_SITE_ID','https://tag.walityk.com');`}
</Script>
</body>
</html>
);
}
Replace YOUR_SITE_ID with the site ID shown in the dashboard (or copy the generated snippet directly — it is identical).
The tag emits automatically on load: page_view (with full context: device, viewport, network, performance), scroll milestones (25/50/75/90%), outbound clicks and form_submit. If you already have a GTM dataLayer, its events are picked up automatically.
Pages Router (alternative)
Same snippet, in pages/_app.tsx:
import type { AppProps } from 'next/app';
import Script from 'next/script';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Script id="walityk" strategy="afterInteractive">
{`(function(s,i,e){
s.__tftq=s.__tftq||[];
s.__tftq.push(['init',{site_id:i,endpoint:e+'/collect'}]);
var t=document.createElement('script');
t.async=true;
t.src=e+'/tag.js';
document.head.appendChild(t);
})(window,'YOUR_SITE_ID','https://tag.walityk.com');`}
</Script>
<Component {...pageProps} />
</>
);
}
3. Track SPA navigations
Next.js navigates client-side without reloading the page: the tag emits the initial page_view, but route changes must be reported to it. Create a small client component:
// app/walityk-page-views.tsx
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useRef } from 'react';
export function WalitykPageViews() {
const pathname = usePathname();
const searchParams = useSearchParams();
const isFirst = useRef(true);
useEffect(() => {
// The tag emits the initial page_view itself on load: don't double it.
if (isFirst.current) {
isFirst.current = false;
return;
}
window.__tftq = window.__tftq || [];
window.__tftq.push(['track', 'page_view']);
}, [pathname, searchParams]);
return null;
}
Mount it in app/layout.tsx (the <Suspense> is required by useSearchParams):
import { Suspense } from 'react';
import { WalitykPageViews } from './walityk-page-views';
// … inside <body>:
<Suspense fallback={null}>
<WalitykPageViews />
</Suspense>
With the Pages Router, use the router’s routeChangeComplete event in _app.tsx:
const router = useRouter();
useEffect(() => {
const onRoute = () => {
window.__tftq = window.__tftq || [];
window.__tftq.push(['track', 'page_view']);
};
router.events.on('routeChangeComplete', onRoute);
return () => router.events.off('routeChangeComplete', onRoute);
}, [router.events]);
TypeScript: declare the queue once in a
global.d.tsfile:declare global { interface Window { __tftq?: unknown[][] } } export {};
4. Track conversions
Push an event via window.__tftq.push(['track', type, properties]) — the call works whether the tag has loaded yet or not:
'use client';
export function SignupButton() {
return (
<button
onClick={() => {
window.__tftq = window.__tftq || [];
window.__tftq.push(['track', 'sign_up', { plan: 'starter' }]);
}}
>
Sign up
</button>
);
}
For a purchase, on the confirmation page:
window.__tftq.push([
'track',
'purchase',
{ value: 49.0, currency: 'EUR', transaction_id: 'ORD-1234' },
]);
The value, currency and transaction_id properties are forwarded as-is to your destinations (GA4, Meta CAPI, Google Ads…).
Standard event types: page_view, view_item, add_to_cart, begin_checkout, purchase, sign_up, login, lead. Any other name goes through as a custom event.
5. Consent
By default the tag runs in cookieless mode: no cookies, no persistent identifier — no banner required. If you have a CMP (Cookiebot, Axeptio, Didomi…), switch to banner mode by adding ,mode:'banner' to the snippet’s init object: the tag starts in Consent Mode v2 deny-default, auto-detects the CMP and only releases events after the visitor’s choice.
6. Verify
- In the dashboard, open your site’s page → View live events.
- Browse your app (locally or in production):
page_viewevents appear in real time. - Trigger a conversion and check it comes through, then check GA4 Realtime if the destination is connected.
Not working?
- No
/collectrequest in DevTools → Network: the snippet isn’t mounted (check the<Script>is in the root layout) or thesite_idis empty. /collectrequest blocked by CORS: the domain the app runs on doesn’t match the domain declared in the dashboard. Locally, test on the declared production or preview domain instead.- Strict CSP: add
https://tag.walityk.comtoscript-srcandconnect-src. - Only one
page_viewper session: the step 3 component isn’t mounted — SPA navigations aren’t being reported.
What’s next?
- Connect your destinations (GA4, Meta CAPI, Google Ads…) from Destinations — no code changes.
- Add your custom events to the site’s tracking plan.
Need help? hi@walityk.com.