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

# Next.js

> Add Bily to Next.js App Router, Pages Router, or a hybrid application.

Add Bily through one client component in each route tree. The same setup supports App Router, Pages Router, and hybrid applications.

Do not use `next/script` or add a raw script tag alongside the SDK. `init()` loads the script for you.

## Install Bily

```bash theme={null}
npm install @bilyai/js
```

## Keep the tracking URL exact

Copy the complete tracking URL from **Bily > Settings > Apps > More settings > Install tracking** into your app's public
build-time settings. The URL is public installation data, not a private token.

For local development, add it to `.env.local`. This file is normally not committed.

```dotenv .env.local theme={null}
NEXT_PUBLIC_BILY_SCRIPT_URL="https://tracking.example.com/b.js?shop=store.example.com"
```

In dotenv files, keep the double quotes so `#` cannot begin a comment. If the exact URL contains a literal dollar sign,
escape only that character as `\$` so Next.js preserves it.

Set the same variable in every preview and production build environment before running `next build`. In a deployment
settings field, enter the raw URL without dotenv quotes or escapes.

Next.js inlines `NEXT_PUBLIC_` values into the browser bundle at build time. Rebuild and redeploy whenever the value changes.

If your production build reads `.env.production`, `.env.production.local`, or another `.env*` file, use the quoted dotenv
form shown above.

Keep the rest of the URL unchanged, including its query string. After changing `.env.local`, restart the local development
server.

## Track Next.js navigation

The browser script records the initial page view automatically. This component remembers that URL and tracks only later client-side Next.js transitions.

```tsx app/bily-tracking.tsx theme={null}
"use client";

import { useEffect, useRef } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { init, track } from "@bilyai/js";

const BILY_SCRIPT_URL = process.env.NEXT_PUBLIC_BILY_SCRIPT_URL;

export function BilyTracking() {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const search = searchParams?.toString() ?? "";
  const previousUrl = useRef<string | null>(null);

  useEffect(() => {
    if (!BILY_SCRIPT_URL) {
      throw new Error("NEXT_PUBLIC_BILY_SCRIPT_URL is required");
    }
    init({ scriptUrl: BILY_SCRIPT_URL });
  }, []);

  useEffect(() => {
    const currentUrl = window.location.href;

    if (previousUrl.current === null) {
      previousUrl.current = currentUrl;
      return;
    }

    if (currentUrl === previousUrl.current) return;
    previousUrl.current = currentUrl;

    track("PageView", {
      sourceUrl: currentUrl,
      pageTitle: document.title,
    });
  }, [pathname, search]);

  return null;
}
```

The null-safe search value supports App Router, Pages Router, and applications that use both.
Comparing URLs prevents route notifications from duplicating the initial page view.

## Mount it once in App Router

Add the component to the root layout. Wrap it in `Suspense` because it reads the current search parameters.

```tsx app/layout.tsx theme={null}
import { Suspense } from "react";
import { BilyTracking } from "./bily-tracking";

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body>
        <Suspense fallback={null}>
          <BilyTracking />
        </Suspense>
        {children}
      </body>
    </html>
  );
}
```

## Cover Pages Router routes

The App Router root layout does not wrap routes in `pages/`. If your application uses Pages Router alone or alongside App
Router, mount the same component once in the Pages Router root.

If `pages/_app.tsx` already exists, add only the Bily mount. Keep its existing imports, providers, layouts, global CSS,
data hooks, and error boundaries. Use this minimal example only when your application does not already have a Custom App.

```tsx pages/_app.tsx theme={null}
import type { AppProps } from "next/app";
import { Suspense } from "react";
import { BilyTracking } from "../app/bily-tracking";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Suspense fallback={null}>
        <BilyTracking />
      </Suspense>
      <Component {...pageProps} />
    </>
  );
}
```

Mount `BilyTracking` once in each route tree. The root layout covers App Router routes. `_app.tsx` covers Pages Router
routes.

Give each environment its own complete URL. Do not build the URL from a hostname or add query parameters in code.

## Track successful actions

Call `track()` from client code only after the operation succeeds.

```tsx app/workspaces/create-workspace-button.tsx theme={null}
"use client";

import { track } from "@bilyai/js";

export function CreateWorkspaceButton() {
  async function handleCreate() {
    const workspace = await createWorkspace();
    track("workspace_created", { workspace_id: workspace.id });
  }

  return <button onClick={handleCreate}>Create workspace</button>;
}
```

<CardGroup cols={2}>
  <Card title="Track page views" icon="window" href="/guides/pageviews" />

  <Card title="Protect client data" icon="shield" href="/privacy/data-safety" />
</CardGroup>
