Sift Docs
Frameworks

Next.js

Install Sift in the App Router or Pages Router without blocking hydration.

Use next/script in the root layout. It loads after hydration, so the script never competes with your app's JavaScript.

App Router

app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <Script
          defer
          data-domain="example.com"
          src="https://sift.agentonboard.xyz/js/sift.js"
          strategy="afterInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

afterInteractive is the right strategy here. beforeInteractive delays your app for a metric, which is the opposite of what an analytics script should do.

Pages Router

pages/_document.tsx
import { Head, Html, Main, NextScript } from "next/document";
import Script from "next/script";

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <Script
          defer
          data-domain="example.com"
          src="https://sift.agentonboard.xyz/js/sift.js"
          strategy="afterInteractive"
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Client-side navigation

The App Router changes the URL with history.pushState. Sift listens for that, so route changes are counted without a reload and without extra code.

One caveat: Sift dedupes identical URLs, not screens. If a transition writes two different URLs for the same screen, both are counted. See SPA routes.

Verify it

Run the app on a real domain, or a preview deployment. localhost is ignored by design. Then open the site's install page in Sift and watch for Signal detected.

Content Security Policy

If you set a CSP, allow the script and the beacon endpoint:

script-src  https://sift.agentonboard.xyz;
connect-src https://sift.agentonboard.xyz;

On this page