Next.js

How to Cloak and Track Affiliate Links in Next.js (No Plugin Needed)

By SK · runs 20+ affiliate sites·11 min read·Updated 2026-07-28

Every WordPress affiliate knows the move: install a link plugin, and it cloaks your ugly affiliate URLs into tidy /go/ links and counts the clicks. On Next.js, there's no plugin to install — you build the redirect yourself. That sounds like a downgrade. It isn't. It's about thirty lines of code, and in exchange you get something no WordPress plugin gives you: the click captured first-party, on your server, before it ever leaves for the merchant. For a business that lives or dies on measurement, that's not a chore — it's the whole point.

The three jobs, same as anywhere

An affiliate link has three jobs to do, exactly as in the WordPress version of this guide: cloak it (a clean, branded URL you can change in one place), count the click, and attribute it (know which page and position drove it). A plugin does the first two and stops at a total. On Next.js, one route handler does all three — and the third is where the value is.

Affiliate redirect flow in Next.jsReader clicks /go/slug, your route handler logs the click first-party, then 302-redirects to the merchant.One route does cloak, count, and attributeYOUR PAGE<a href="/go/dyson">click/go/[slug] ROUTE1 · look up the destination2 · log the clickCLICK CAPTURED — FIRST-PARTY302MERCHANTAmazon, etc.with your tagNo plugin needed — and because you own the redirect, the click is yours before it ever leaves your site.
A single Next.js route handler cloaks the link, logs the click first-party, and redirects to the merchant — the attribution layer WordPress plugins stop short of.

The build: one route handler

Point your pages at /go/<slug> instead of the raw affiliate URL, then create a single dynamic route that looks up the destination, logs the click, and redirects. In the App Router (Next 15/16, where params is async):

// app/go/[slug]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { links } from "@/lib/affiliate-links";
import { recordClick } from "@/lib/analytics";

export async function GET(
  req: NextRequest,
  { params }: { params: Promise<{ slug: string }> }
) {
  const { slug } = await params;
  const target = links[slug];

  // unknown slug: send them home rather than error
  if (!target) return NextResponse.redirect(new URL("/", req.url), 302);

  // capture the click first-party — do not await, so the redirect stays fast
  recordClick({
    slug,
    from: req.headers.get("referer"),
    ua: req.headers.get("user-agent"),
    at: Date.now(),
  });

  return NextResponse.redirect(target, 302);
}

The link map is plain config you can edit in one place (or move to a database as it grows):

// lib/affiliate-links.ts
export const links: Record<string, string> = {
  "dyson-tp09": "https://www.amazon.com/dp/B0XXXXXXXX?tag=yoursite-20",
  "ninja-af101": "https://www.amazon.com/dp/B0YYYYYYYY?tag=yoursite-20",
};

That's cloaking (clean /go/ URLs), counting (every click flows through recordClick), and the redirect — done. Change a destination once in the map and every link across your site updates.

Why owning the redirect changes everything

Here's the part a plugin can't match. Because you wrote the handler, the click is yours — first-party, server-side, captured before the visitor leaves. Look at what recordClick already has access to: the referer tells you which page sent the click; you can pass the slug's position from the link itself; the timestamp gives you real-time. Send that to your own database or analytics and you can answer the question the network dashboard and the plugin counter never can — which page and which placement actually earn — which is the entire premise of revenue attribution and the affiliate analytics stack.

A WordPress plugin gives you a click total. This gives you a click record, first-party, that you control. That gap is the difference between knowing you got clicks and knowing where your money comes from.

Getting the details right

A few things worth doing properly:

  • Keep your tracking tag on the destination. The route controls the path; the Amazon tracking ID (or network parameters) on the target URL is what actually attributes the sale. The redirect doesn't replace it.
  • Handle unknown slugs. Redirect home or return a 404 rather than erroring — links rot, and a broken /go/ link shouldn't throw.
  • Don't block the redirect. Fire recordClick without await (or use a queue) so logging never slows the hop to the merchant.
  • Keep it out of the index. The /go/ route is functional, not content — don't link to it from sitemaps, and disallow it in robots if you like.
  • Disclosure still applies. Cloaking a link doesn't remove the obligation to disclose — generate one with the disclosure tool.

Next.js vs WordPress, honestly

WordPress wins on convenience: a plugin is faster than writing code, and if you never need per-page attribution, it's enough. Next.js wins on control and measurement: you own the redirect, so you own the data, and the click record is as detailed as you care to make it. For an affiliate operator who treats the site as a business to be measured — which is the whole thesis of this hub — that ownership is worth the thirty lines. You're not missing a plugin; you're skipping the ceiling it would have put on your data.

Turn those click records into revenue: Clickolytics connects the clicks your redirect route captures to the commissions they earn, so per-page attribution becomes a number you can act on. See how it works →

Frequently asked questions

How do I cloak links without a plugin? A dynamic route — app/go/[slug]/route.ts — that maps a slug to the real URL and 302-redirects. Link to /go/slug on your pages.

Why is this better than a plugin? You own the redirect, so you capture the click first-party (page, position, time) before it leaves — the attribution a plugin's counter can't give.

Does it hurt SEO? No — internal /go/ links and a 302 to the merchant are standard; just keep the route out of your indexable content.

Do I still need my Amazon tag? Yes — the tag on the destination URL is what attributes the sale; the route only cloaks and counts.

Related reading