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

# Next.js

> Install the Cashback Hive SDK in a Next.js App Router project using next/script, with a typed useCashbackHive hook that waits for the SDK to be ready.

Use Next.js's built-in [`next/script`](https://nextjs.org/docs/app/api-reference/components/script) component. It de-duplicates the script across client-side navigations and gives you control over when it loads.

**1. Add the script to the root layout**

Place it once, in the root layout, so it loads for every route.

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

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          id="cashback-hive-sdk"
          src="https://sdk.cashbackhive.com/sdk.js"
          data-key="cbh_pk_your_publishable_key"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}
```

`strategy="afterInteractive"` (the default) loads the script after hydration, which is right for the SDK. Don't use `beforeInteractive` — the SDK isn't needed before your app is interactive, and that strategy blocks other scripts.

> [!TIP]
>
> The Pages Router works the same way: put the `<Script>` in `pages/_app.tsx`.

**2. Add type declarations**

Declare the global so you can use `window.CashbackHive` without `any`. Copy the complete declaration file from the [Types reference](https://developer.cashbackhive.com/reference/types.md#typescript-declarations) into your project, for example at `types/cashback-hive.d.ts`.

Make sure the file is included by your `tsconfig.json` (the default `"include": ["**/*.ts", "**/*.tsx"]` covers it).

**3. Create a readiness hook**

Components render during hydration, before `afterInteractive` scripts run, so a simple `useEffect` listener usually works — but a component that mounts after a client-side navigation would wait forever for an event that already fired. `useSyncExternalStore` handles both cases cleanly and satisfies the React Compiler lint rules.

```ts hooks/use-cashback-hive.ts theme={null}
"use client";

import { useSyncExternalStore } from "react";
import type { CashbackHiveSdk } from "@/types/cashback-hive";

function subscribe(onStoreChange: () => void) {
  window.addEventListener("cbh:ready", onStoreChange);
  return () => window.removeEventListener("cbh:ready", onStoreChange);
}

function getSnapshot(): CashbackHiveSdk | null {
  const sdk = window.CashbackHive;
  return sdk?.initialized ? sdk : null;
}

function getServerSnapshot(): CashbackHiveSdk | null {
  return null;
}

/** Returns the SDK once it's initialised, or null before that. */
export function useCashbackHive(): CashbackHiveSdk | null {
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}
```

**4. Use it in a client component**

```tsx components/offers.tsx theme={null}
"use client";

import { useEffect, useState } from "react";
import { useCashbackHive } from "@/hooks/use-cashback-hive";
import type { CbhOffer } from "@/types/cashback-hive";

export default function Offers() {
  const sdk = useCashbackHive();
  const [offers, setOffers] = useState<CbhOffer[]>([]);

  useEffect(() => {
    if (!sdk) return;
    let cancelled = false;
    sdk.offers.list({ limit: 10 }).then((res) => {
      if (!cancelled) setOffers(res.offers);
    });
    return () => {
      cancelled = true;
    };
  }, [sdk]);

  if (!sdk) return <p>Loading…</p>;

  return (
    <ul>
      {offers.map((offer) => (
        <li key={offer.slug}>
          {offer.name}
          <button onClick={() => sdk.openOffer(offer)}>Shop</button>
        </li>
      ))}
    </ul>
  );
}
```

Server Components can't access `window`, so anything that calls the SDK must be a Client Component (`"use client"`). You can still render the Client Component from a Server Component page.

## Environment variables

If you prefer not to hardcode the key, read it from a `NEXT_PUBLIC_` variable. This is safe **only** for the publishable key.

```tsx theme={null}
<Script
  id="cashback-hive-sdk"
  src="https://sdk.cashbackhive.com/sdk.js"
  data-key={process.env.NEXT_PUBLIC_CASHBACK_HIVE_KEY}
  strategy="afterInteractive"
/>
```

> [!WARNING]
>
> Never put a secret key in a `NEXT_PUBLIC_*` variable — those are inlined into the client bundle.

## Content Security Policy

If your app sets a CSP (via `next.config.js` headers or middleware), add `https://sdk.cashbackhive.com` to `script-src` and `connect-src`. See [Content Security Policy](https://developer.cashbackhive.com/guides/content-security-policy.md).

## Complete example

A working Next.js App Router integration — layout, hook, types, and Offers/Activate widgets — is in the [Widgets](https://developer.cashbackhive.com/widgets/overview.md) section.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.