Next.js
Add Pieowl to a Next.js app with the script tag, report AI crawlers with @pieowl/bots, and send server-side events with @pieowl/sdk.
Looking for a step-by-step tutorial? Read the Next.js analytics guide.
Good to know
A Next.js app needs up to three pieces. Use the ones you need:
- Pages and visitors: the HTML script, loaded with
next/script. It tracks client-side route changes in the App Router and the Pages Router. - AI crawlers and bots:
@pieowl/botsinproxy.ts, which reports the bots the script can't see. - Server-side events (route handlers, server actions):
@pieowl/sdk.
Track pages
Add the script to your root layout and replace YOUR_CLIENT_ID with your client ID. The first script creates a small queue, so calls made before pieowl.js loads (for example in useEffect) are sent once it's ready.
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script id="pieowl-queue">{`window.pieowl=window.pieowl||function(){(window.pieowl.q=window.pieowl.q||[]).push(arguments)};`}</Script>
<Script src="https://pieowl.com/pieowl.js" data-client-id="YOUR_CLIENT_ID" />
</body>
</html>
);
}With the Pages Router, put the same two Script tags in pages/_app.tsx.
What the script collects by itself (page views, outside link clicks, data-track clicks, session replay) is set in Settings → Site → What Pieowl counts, not in code. For options that need code, see Options in code.
Tracking events
In client components, call window.pieowl, or add data-track attributes to buttons and links. data-track needs Clicks you marked in your code to be on in Settings → Site (it is by default).
'use client';
export function SignupButton() {
return (
<button type="button" onClick={() => window.pieowl('track', 'signup_clicked', { location: 'header' })}>
Sign up
</button>
);
}For TypeScript, declare the global once:
declare global {
interface Window {
pieowl: (method: string, ...args: unknown[]) => void;
}
}
export {};Identifying users
window.pieowl('identify', {
profileId: user.id, // Required
email: user.email,
properties: { tier: 'premium' },
});setGlobalProperties, increment, decrement, upsertGroup, setGroup and clear work the same way. See the Script Tag docs.
Track AI crawlers and bots
npm install @pieowl/botsimport { trackBots } from '@pieowl/bots/next';
export const proxy = trackBots({
clientId: process.env.PIEOWL_CLIENT_ID!,
clientSecret: process.env.PIEOWL_CLIENT_SECRET!,
});On Next.js 15 or older, name the file middleware.ts and use export const middleware = trackBots({ ... }). It never blocks or changes the response. Already have a proxy, or want a matcher? See AI crawlers and bots.
Avoid ad blockers
Serve the script and send events through your own domain:
import { createRouteHandler } from '@pieowl/bots/route-handler';
export const { GET, POST } = createRouteHandler();Then switch the two Script tags from Track pages to the init form: call init with apiUrl: '/api/pieowl' after the queue line, and load the script from /api/pieowl/pieowl.js without data-client-id.
The handler serves pieowl.js and forwards /track calls with the visitor's IP and user agent, so locations and devices stay correct. It serves only pieowl.js: with session replay, set sessionReplay.scriptUrl to https://pieowl.com/pieowl-replay.js.
Server-side events
To track events from server components, route handlers or server actions, use the JavaScript SDK.
Server events must include your client secret. Browsers prove where they come from with CORS headers, but servers can't, so the secret authenticates the request. Use the same client ID as in the script, add its client secret, and keep this file on the server.
npm install @pieowl/sdkimport { Pieowl } from '@pieowl/sdk';
export const pieowlServer = new Pieowl({
clientId: process.env.PIEOWL_CLIENT_ID!,
clientSecret: process.env.PIEOWL_CLIENT_SECRET!,
});
// Pass `profileId` to track the event for a specific user
pieowlServer.track('my_server_event', { profileId: '123', ok: true });Serverless and Vercel
In serverless functions, use waitUntil so the function stays alive until the event is sent. Read more in the Vercel docs.
import { waitUntil } from '@vercel/functions';
import { pieowlServer } from '@/utils/pieowl-server';
export function GET() {
waitUntil(pieowlServer.track('my_server_event', { foo: 'bar' }));
return new Response('Your event has been logged!');
}