# Next.js (/docs/developer/nextjs)





# Next.js [#nextjs]

Ucoder Insight supports both the **App Router** and the **Pages Router**. Initialize the SDK once, near the root of your application, using the pattern for your router below.

<Callout type="info">
  Using plain React (no Next.js)? See the [React guide](/docs/developer/react) instead.
</Callout>

## Setup [#setup]

<Tabs items="['App Router', 'Pages Router']" groupId="nextjs-router">
  <Tab value="App Router">
    Create a client component and mount it once in your root layout.

    ```tsx title="app/analytics.tsx"
    "use client";

    import { useEffect } from "react";
    import { initUcoderInsight } from "ucoder-insight";

    export function Analytics() {
      useEffect(() => {
        void initUcoderInsight("YOUR_PUBLIC_TRACKING_ID");
      }, []);

      return null;
    }
    ```

    ```tsx title="app/layout.tsx"
    import { Analytics } from "./analytics";

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

    The `"use client"` directive is required — `initUcoderInsight` runs in the browser and cannot execute in a Server Component.
  </Tab>

  <Tab value="Pages Router">
    Initialize the SDK once in `_app.tsx`, which mounts for every page.

    ```tsx title="pages/_app.tsx"
    import { useEffect } from "react";
    import type { AppProps } from "next/app";
    import { initUcoderInsight } from "ucoder-insight";

    export default function App({ Component, pageProps }: AppProps) {
      useEffect(() => {
        void initUcoderInsight("YOUR_PUBLIC_TRACKING_ID");
      }, []);

      return <Component {...pageProps} />;
    }
    ```

    Do not initialize the SDK separately inside individual pages.
  </Tab>
</Tabs>

<Callout type="warn">
  In development, React's `StrictMode` intentionally mounts effects twice.
  You may see `initUcoderInsight` run twice in the console — this is
  expected and does not cause duplicate tracking in production.
</Callout>

## Tracking route changes [#tracking-route-changes]

Ucoder Insight automatically tracks page views on navigation by patching `history.pushState` / `history.replaceState` and listening for `popstate` — see [Tracking route changes in a single-page app](/docs/developer/react#tracking-route-changes-in-a-single-page-app) for how this works. Since Next.js client-side navigation (via `<Link>`, `useRouter().push()`, etc.) uses the History API under the hood, this works automatically in both routers without extra code.

<Callout type="info">
  If you notice route changes that aren't appearing as page views — for
  example, during heavy use of prefetching or streaming in the App Router —
  you can track a page view manually as a fallback. See below for
  router-specific patterns.
</Callout>

### App Router: manual fallback [#app-router-manual-fallback]

If you need to guarantee a page view fires on every route change, track it explicitly using `usePathname` and `useSearchParams`:

```tsx title="app/analytics.tsx"
"use client";

import { useEffect } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { initUcoderInsight, trackPageView } from "ucoder-insight";

export function Analytics() {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    void initUcoderInsight("YOUR_PUBLIC_TRACKING_ID");
  }, []);

  useEffect(() => {
    const url = searchParams?.toString()
      ? `${pathname}?${searchParams.toString()}`
      : pathname;
    trackPageView(url);
  }, [pathname, searchParams]);

  return null;
}
```

`useSearchParams()` requires the component to be wrapped in a `<Suspense>` boundary in the App Router — wrap `<Analytics />` in `<Suspense fallback={null}>` if you see a build warning about this.

### Pages Router: manual fallback [#pages-router-manual-fallback]

Use the `next/router` events API to fire a page view on every completed route change:

```tsx title="pages/_app.tsx"
import { useEffect } from "react";
import { useRouter } from "next/router";
import type { AppProps } from "next/app";
import { initUcoderInsight, trackPageView } from "ucoder-insight";

export default function App({ Component, pageProps }: AppProps) {
  const router = useRouter();

  useEffect(() => {
    void initUcoderInsight("YOUR_PUBLIC_TRACKING_ID");
  }, []);

  useEffect(() => {
    const handleRouteChange = (url: string) => trackPageView(url);
    router.events.on("routeChangeComplete", handleRouteChange);
    return () => {
      router.events.off("routeChangeComplete", handleRouteChange);
    };
  }, [router.events]);

  return <Component {...pageProps} />;
}
```

See [Configuration](/docs/getting-started/configuration) for supported SDK options.
