RefearnApp RefearnApp
Tutorial Next.js Stripe

How to Build an Affiliate Program in Next.js (The Clean Way)

How to Build an Affiliate Program in Next.js (The Clean Way)

Introduction

You’re shipping a Next.js SaaS. You want affiliates. You look at Rewardful — $49/month. FirstPromoter — $89/month. Impact — “contact sales.” All of them to do one thing: track a ?ref= query param and attribute a Stripe payment to it.

That’s it. That’s the core problem. You’re paying three figures a month for a cookie and a dashboard.

This guide shows you how to implement affiliate tracking yourself — the right way — and introduces a free, self-hosted alternative that handles the rest of the infrastructure you don’t want to build.


The Core Logic of Affiliate Tracking in Next.js

Affiliate tracking boils down to three straightforward steps:

Step 1

Capture

The ?ref= query parameter on landing

Step 2

Persist

In a cookie to survive page navigation

Step 3

Pass

To Stripe Checkout during session creation

Capturing the Referral Param

In the Next.js App Router, you can’t use useSearchParams directly inside Server Components. You have two clean paths to handle this execution layer:

Option A — Client Component with useSearchParams

Create an invisible component that mounts on the client and reads the layout context:

components/RefTracker.tsx
tsx
'use client';

      import { useSearchParams } from 'next/navigation';
      import { useEffect } from 'react';
      import Cookies from 'js-cookie';

      export function RefTracker() {
        const searchParams = useSearchParams();

        useEffect(() => {
          const ref = searchParams.get('ref');

          if (ref) {
            Cookies.set('affiliate_ref', ref, {
              expires: 30,
              sameSite: 'lax',
              secure: process.env.NODE_ENV === 'production',
            });
          }
        }, [searchParams]);

        return null;
      }

Drop this component straight into your root layout context, keeping it safely wrapped in a Suspense boundary to avoid breaking server-side rendering:

app/layout.tsx
tsx
import { Suspense } from 'react';
import { RefTracker } from '@/components/RefTracker';

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

              {children}
            </body>
          </html>
        );
      }

Option B — Middleware (Zero Client JS)

Alternatively, intercept the inbound request edge directly using a Next.js middleware match pattern:

middleware.ts
ts
import { NextRequest, NextResponse } from 'next/server';

      export function middleware(request: NextRequest) {
        const response = NextResponse.next();

        const ref = request.nextUrl.searchParams.get('ref');

        if (ref && !request.cookies.has('affiliate_ref')) {
          response.cookies.set('affiliate_ref', ref, {
            maxAge: 60 * 60 * 24 * 30,
            sameSite: 'lax',
            secure: process.env.NODE_ENV === 'production',
          });
        }

        return response;
      }

      export const config = {
        matcher: ['/((?!_next|api|favicon.ico).*)'],
      };

Middleware is the better default choice here. It functions entirely server-side, runs before pages even execute their paint cycles, and eliminates runtime layout shift risks or client-side hydration burdens.

Why a Cookie, Not localStorage?

  • Server-Side Readability: Your Next.js Route Handlers and Server Actions can access incoming cookies instantly during a checkout call before rendering responses.

  • Path and Subdomain Scoping: Cookies can be accessed seamlessly across root domains and subdomains alike.

  • Resilience to Redirects: Third-party payment gateways like Stripe Checkout force external window re-routes. Cookies safely survive full-page handoffs.


Step-by-Step Code Implementation

Step 1 — Full Ref Capture + Cookie (TypeScript)

middleware.ts
ts
import { NextRequest, NextResponse } from 'next/server';

      const REF_COOKIE = 'affiliate_ref';
      const REF_MAX_AGE = 60 * 60 * 24 * 30;

      function isValidRef(ref: string): boolean {
        return /^[a-zA-Z0-9-]{3,32}$/.test(ref);
      }

      export function middleware(request: NextRequest) {
        const response = NextResponse.next();

        const ref = request.nextUrl.searchParams.get('ref');

        if (
          ref &&
          isValidRef(ref) &&
          !request.cookies.has(REF_COOKIE)
        ) {
          response.cookies.set(REF_COOKIE, ref, {
            maxAge: REF_MAX_AGE,
            httpOnly: true,
            sameSite: 'lax',
            secure: process.env.NODE_ENV === 'production',
            path: '/',
          });
        }

        return response;
      }

Step 2 — Pass the Ref to Stripe Checkout

app/api/checkout/route.ts
ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripe = new Stripe(
process.env.STRIPE_SECRET_KEY!,
{
  apiVersion: '2024-04-10',
}
);

      export async function POST(request: NextRequest) {
        const affiliateRef =
          request.cookies.get('affiliate_ref')?.value ?? null;

        const session =
          await stripe.checkout.sessions.create({
            mode: 'subscription',

            line_items: [
              {
                price: process.env.STRIPE_PRICE_ID!,
                quantity: 1,
              },
            ],

            success_url:
              `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,

            cancel_url:
              `${process.env.NEXT_PUBLIC_URL}/pricing`,

            metadata: {
              affiliate_ref: affiliateRef ?? '',
            },
          });

        return NextResponse.json({
          url: session.url,
        });
      }

The Infrastructure Problem: Self-Hosting vs. SaaS

Feature Block

Custom Infrastructure

SaaS Platform Strategy

Affiliate Portal

High — Requires multi-tenant analytics UIs

$49 - $99/mo subscriptions

Payout Engines

Complex — Generating tax ledger lines

Locked behind premium tiers

Enter RefearnApp

Core Platform Benefits:

  • Native Affiliate Dashboards: Monitor conversion tracking metrics out of the box.

  • Full Data Sovereignty: Hosted inside your private ecosystem. No external script taxes.

  • Zero Monthly Fees: Scale your referral systems horizontally without operational pricing expansion.


Conclusion & Next Steps

Building an affiliate system doesn’t mean writing complex tracking matrices from scratch or paying heavy SaaS platform taxes. By managing the param edge directly via middleware, you maintain ultra-low latency execution over your conversion funnels.

Ready to Build Without Subscription Limits?

RefearnApp is fully open-source, self-hostable, and AGPL-3.0 licensed. You can deploy it completely free on your own infrastructure using Coolify or follow our step-by-step setup walkthrough.

Developer Choice

Self-Hosted Open Source

Deploy directly on your own VPS via Coolify. Enjoy 100% data ownership.

Production Ready

Managed RefearnApp Cloud

Get up and running instantly. Skip the manual environment configuration, provisioning, and setup tasks with a fully turnkey, zero-config deployment.

Try Managed Cloud

Launch your affiliate program today

Create an affiliate program for your SaaS or digital product in minutes.

LIFETIME DEALBuy Once, Own Forever
or
Start for Free

No credit card required