> ## Documentation Index
> Fetch the complete documentation index at: https://docs.traceten.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js

> Add Traceten to a Next.js app using the App Router layout or Pages Router _document.tsx.

## What this lets you do

Track AI-referred traffic across a Next.js application, including client-side navigations that don't trigger a full page reload.

## Before you start

* Your Traceten site key, from **Sites → Install** in the [dashboard](https://app.traceten.com/dashboard).
* A Next.js project (version 13 or later recommended).
* Understand which router you're using: App Router (`app/`) or Pages Router (`pages/`).

## App Router (Next.js 13+)

The App Router uses a root layout file (`app/layout.tsx`) that wraps every route. This is where the snippet goes.

<Steps>
  <Step title="Open app/layout.tsx">
    Open your root layout file at `app/layout.tsx` (or `app/layout.jsx`).
  </Step>

  <Step title="Add the snippet to the <head>">
    Use Next.js's built-in `<Script>` component with `strategy="afterInteractive"`. This is equivalent to adding an `async` script tag and ensures the snippet loads after the page is interactive.

    ```tsx theme={null}
    import Script from "next/script";

    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return (
        <html lang="en">
          <head />
          <body>
            {children}
            <Script
              src="https://cdn.traceten.com/tt.min.js"
              data-site="ttid_7Rb4TrC1dTbnD8w3s1TS12"
              data-cookie-domain="example.com"
              strategy="afterInteractive"
            />
          </body>
        </html>
      );
    }
    ```

    Copy the whole tag from **Sites → Install** for your site so `data-site` (your site key) is already yours. `data-cookie-domain` is your own domain, which keeps one visitor as one visitor across your subdomains. It's off by default, so it only appears on the tag once you've confirmed a value under **Sites → Settings → Cookies**. See [Subdomains and cross-domain](/install/subdomains).

    `strategy="afterInteractive"` loads the script after hydration without blocking the page. Do not use `strategy="beforeInteractive"`, because the snippet is designed to run after the page loads.
  </Step>

  <Step title="Deploy and verify">
    Run `next build` and deploy, or start the dev server with `next dev` to test locally. Then check the **Verify** step on your site's install page.
  </Step>
</Steps>

## Pages Router

If you're using the Pages Router (`pages/` directory), the snippet goes in `_document.tsx`.

<Steps>
  <Step title="Open or create pages/_document.tsx">
    If the file doesn't exist, create it at `pages/_document.tsx`.
  </Step>

  <Step title="Add the snippet in the Head component">
    ```tsx theme={null}
    import { Html, Head, Main, NextScript } from "next/document";

    export default function Document() {
      return (
        <Html lang="en">
          <Head>
            {/* Traceten AI traffic attribution */}
            <script
              async
              src="https://cdn.traceten.com/tt.min.js"
              data-site="ttid_7Rb4TrC1dTbnD8w3s1TS12"
              data-cookie-domain="example.com"
            />
          </Head>
          <body>
            <Main />
            <NextScript />
          </body>
        </Html>
      );
    }
    ```

    Copy the whole tag from **Sites → Install** for your site so `data-site` (your site key) is already yours. `data-cookie-domain` is your own domain, which keeps one visitor as one visitor across your subdomains. It's off by default, so it only appears on the tag once you've confirmed a value under **Sites → Settings → Cookies**. See [Subdomains and cross-domain](/install/subdomains).
  </Step>

  <Step title="Deploy and verify">
    Restart your dev server or deploy. Check the **Verify** step on your site's install page.
  </Step>
</Steps>

## Single-page application navigation

**Route changes are tracked automatically. You do not need to add anything.**

The snippet watches `location` and fires a pageview whenever the path or query
string changes, so App Router and Pages Router navigations are both picked up
without any code from you. Each detected navigation closes the measurement
window for the route being left, reports it, and opens a fresh one (scroll
depth, time on page, active time) for the new route.

<Warning>
  **Do not also call `window.traceten("pageview")` on route changes.** Earlier versions of this page
  recommended a `usePathname` effect or a `routeChangeComplete` handler. Following that advice now
  double-counts: the snippet fires its own pageview and yours fires a second one, so one navigation
  produces two pageviews and an extra billable request. If you added either pattern, remove it.
</Warning>

Two cases still need a manual call:

* **Hash-only routing.** Changes to the fragment (`/docs#install` →
  `/docs#usage`) are deliberately ignored, because most sites use the hash for
  in-page anchors rather than for navigation. If your router navigates by hash
  alone, call `window.traceten("pageview")` yourself after each change.
* **A route that changes neither path nor query string.** Rare, but if you
  re-render a genuinely different page at the same URL, the snippet cannot see
  it.

Calling it twice for the same URL is safe against inflated engagement figures:
the snippet only closes a measurement window when the URL actually changed. It
is still an extra request, so avoid it.

See the [browser API reference](/sdks/browser) for everything else
`window.traceten` exposes.

## Environment-specific site keys

Use different site keys for production and staging:

```tsx theme={null}
// app/layout.tsx
const SITE_KEY = process.env.NEXT_PUBLIC_TRACETEN_SITE_KEY;

{
  /* Only render the snippet if the key is configured */
}
{
  SITE_KEY && (
    <Script
      src="https://cdn.traceten.com/tt.min.js"
      data-site={SITE_KEY}
      data-cookie-domain="example.com"
      strategy="afterInteractive"
    />
  );
}
```

In `.env.local` (staging):

```
NEXT_PUBLIC_TRACETEN_SITE_KEY=ttid_9Fq2WmXe4vLpNs7RaT1KcZ
```

In your production environment variables:

```
NEXT_PUBLIC_TRACETEN_SITE_KEY=ttid_7Rb4TrC1dTbnD8w3s1TS12
```

`data-cookie-domain` can stay hardcoded across both environments. A staging host under the same domain (`staging.example.com`) is covered by it, and a preview host on a different domain (`*.vercel.app`) does not match, so the snippet ignores it there and writes host-only cookies. If your staging site is a separate Traceten site on its own domain, use the value its own install page shows.

Do not commit real site keys to version control.

## Verify installation

Open your app in a browser, then open **Sites → Install** in the [dashboard](https://app.traceten.com/dashboard) and look at the **Verify** step. It should turn green within 30 seconds of a pageview.

## Next steps

* [Verify the snippet is firing](/install/verify)
* [Troubleshooting](/install/troubleshooting)
