Rekomi Docs
For brandsInstall tracking
Brands

Install on Next.js Pages Router

Use next/script in pages/_app.tsx. Raw script tags in _document.tsx fall outside Next's script optimization. Pass the affiliate slug from the client via window.Rekomi.getReferral().

The Pages Router (Next.js's pre-App-Router pattern, still supported through Next 16) installs Rekomi via the next/script component in _app.tsx so the loader runs on every page render. Don't paste raw <script> tags in _document.tsx; they load outside Next's script optimization, so you lose strategy control. Sales are recorded off the page (through your gateway or a server-to-server call), so there is no browser convert event to fire.

Install in pages/_app.tsx

Edit pages/_app.tsx and add the <Script> component inside the App component's return:

import type { AppProps } from "next/app";
import Script from "next/script";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <Script
        id="rekomi"
        src="https://api.rekomi.com/api/v1/r/loader.js"
        strategy="afterInteractive"
        data-program-id={process.env.NEXT_PUBLIC_REKOMI_PROGRAM_ID}
      />
    </>
  );
}

strategy="afterInteractive" defers loading until after hydration, which is the right balance for click capture.

Set NEXT_PUBLIC_REKOMI_PROGRAM_ID in .env.local (and your hosting env vars). The NEXT_PUBLIC_ prefix is required for browser exposure.

Why not _document.tsx?

In older Next.js (pre-14), _document.tsx was a common spot for third-party scripts via <Head> content. Raw script tags in _document.tsx load outside Next's script optimization, so you lose strategy control and the tag is easy to duplicate or drop during upgrades. Use next/script in _app.tsx, which integrates with the loading lifecycle. Stick with _app.tsx.

Recording the sale

The browser pixel no longer records sales, so there is no convert call to fire from a success page. A sale is recorded through one of these paths:

  • Your payment gateway. Connect it once in the Connect payment gateway step at /dashboard/setup/connect-sales (Stripe, Paddle, Braintree, Shopify, Wix, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, or Dodo Payments) and sales, refunds, and cancellations record automatically.
  • Server-to-server. POST the sale from an API route to Rekomi's S2S endpoint with an HMAC signature. See Server-to-server tracking.
  • Coupon codes. Give each affiliate a unique discount code (Campaign > Coupons); a redeemed code credits them at checkout.

Stripe Checkout from a Pages Router app

The standard pattern: a Pages API route at pages/api/create-checkout-session.ts creates the Stripe Session server-side, then redirects the browser to the Stripe-hosted URL. The Rekomi referral travels via an HTTP header from the browser (where window.Rekomi.getReferral() reads it) to your API route:

// pages/api/create-checkout-session.ts
import type { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== "POST") return res.status(405).end();

  const referral = req.headers["x-rekomi-referral"] as string | undefined;
  const { priceId } = req.body;

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
    subscription_data: referral
      ? { metadata: { rekomi_affiliate_slug: referral } }
      : undefined,
  });

  res.json({ url: session.url });
}

The browser-side code that calls this endpoint reads the referral from window.Rekomi.getReferral() and forwards it as the x-rekomi-referral header:

async function startCheckout(priceId: string) {
  const referral = window.Rekomi?.getReferral?.();
  const res = await fetch("/api/create-checkout-session", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(referral ? { "x-rekomi-referral": referral } : {}),
    },
    body: JSON.stringify({ priceId }),
  });
  const { url } = await res.json();
  window.location.href = url;
}

Capturing leads (optional)

window.Rekomi.convert() is now a LEAD capture call, not a sale. On a signup page you can tie a customer email to the referral before payment:

import { useEffect } from "react";

export default function SignupThanks({ email }: { email: string }) {
  useEffect(() => {
    if (email) window.Rekomi?.ready?.(() => window.Rekomi.convert(email));
  }, [email]);

  return <div>Thanks for signing up!</div>;
}

Browser lead capture works on every plan; the server-side lead endpoint requires Starter or higher. See Track leads and signups.

Quirks worth knowing

next/script belongs in _app.tsx, not _document.tsx. Don't follow older Stack Overflow answers that put scripts in _document.tsx <Head>. That pattern loses next/script's strategy control, and the tag is easy to duplicate or drop during upgrades.

strategy="afterInteractive" is the right choice. Same as App Router: load after hydration but before idle.

The referral is client-side only. The loader writes to localStorage, so read it with window.Rekomi.getReferral() in the browser and forward it as a header or body field. There is no server-readable rekomi_via cookie, so getServerSideProps cannot read the referral from context.req.cookies; pass it from the client as shown above.

Troubleshooting

window.Rekomi is undefined when calling from an effect. The Script hasn't loaded yet. Use window.Rekomi?.ready?.(cb) to defer until the loader is ready, or guard with if (window.Rekomi).

API route returns 500. The STRIPE_SECRET_KEY env var is missing. Set it in .env.local and your hosting env vars.

Script tag appears but no referral captured. CSP blocking, or the loader ran on a page the affiliate did not send traffic to. Check the browser console for CSP violations and confirm getReferral() returns a slug on the landing page.

rekomi_affiliate_slug is missing from the subscription's metadata. The header didn't carry a referral. Confirm the browser reads window.Rekomi.getReferral() and forwards it as x-rekomi-referral, and that the loader captured a referral on the landing page.