# Next.js

URL: https://pieowl.com/docs/sdks/nextjs





<Callout>
  Looking for a step-by-step tutorial? Read the [Next.js analytics guide](/guides/nextjs-analytics).
</Callout>

Good to know [#good-to-know]

A Next.js app needs up to three pieces. Use the ones you need:

* **Pages and visitors**: the [HTML script](/docs/sdks/script), loaded with `next/script`. It tracks client-side route changes in the App Router and the Pages Router.
* **AI crawlers and bots**: `@pieowl/bots` in `proxy.ts`, which reports the bots the script can't see.
* **Server-side events** (route handlers, server actions): `@pieowl/sdk`.

Track pages [#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.

```tsx title="app/layout.tsx"
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](/docs/sdks/script#options-in-code).

Tracking events [#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).

```tsx title="components/signup-button.tsx"
'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:

```ts title="types/pieowl.d.ts"
declare global {
  interface Window {
    pieowl: (method: string, ...args: unknown[]) => void;
  }
}
export {};
```

Identifying users [#identifying-users]

<PersonalDataWarning />

```tsx
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/sdks/script#usage) docs.

Track AI crawlers and bots [#track-ai-crawlers-and-bots]

```bash
npm install @pieowl/bots
```

```ts title="proxy.ts"
import { 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](/docs/ai-bots#nextjs).

Avoid ad blockers [#avoid-ad-blockers]

Serve the script and send events through your own domain:

```ts title="app/api/pieowl/[...pieowl]/route.ts"
import { createRouteHandler } from '@pieowl/bots/route-handler';

export const { GET, POST } = createRouteHandler();
```

Then switch the two `Script` tags from [Track pages](#track-pages) to the [`init` form](/docs/sdks/script#options-in-code): 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](/docs/session-replay), set `sessionReplay.scriptUrl` to `https://pieowl.com/pieowl-replay.js`.

Server-side events [#server-side-events]

To track events from server components, route handlers or server actions, use the [JavaScript SDK](/docs/sdks/javascript).

<Callout>
  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.
</Callout>

```bash
npm install @pieowl/sdk
```

```typescript title="utils/pieowl-server.ts"
import { 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 [#serverless-and-vercel]

In serverless functions, use `waitUntil` so the function stays alive until the event is sent. Read more in the [Vercel docs](https://vercel.com/docs/functions/functions-api-reference#waituntil).

```typescript
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!');
}
```
