Skip to main content
ParityEdge
Next.js 14/15 App Router
Framework SDK

Next.js 15 App Router (parityedge)

Add dynamic country discounts to your Next.js 15 App Router project in under 3 minutes. Show automatic localized banners to visitors in emerging markets and prefill discounts on Stripe or Lemon Squeezy checkout links without any page flicker or layout shifts.

Complete Tutorial Available: Read our comprehensive guide to implementing PPP in Next.js App Router with Stripe.
Read Blog Guide

Installation

npm package ↗
npm install parityedge

Client Component Integration (App Router)

Choose one of two patterns depending on whether you need access to parity data in your own components.

Pattern A — Banner only (no custom UI needed)

Drop <ParityBanner> directly into any Client Component. It handles its own fetch internally — no provider required.

// app/pricing/page.tsx
'use client';

import { ParityBanner } from 'parityedge/react';

export default function PricingPage() {
  return (
    <main className="max-w-4xl mx-auto py-12 px-4">
      <ParityBanner
        projectId="prj_live_YOUR_PROJECT_ID"
        theme="dark"
        position="bottom-pill"
      />
      <h1 className="text-3xl font-bold">Pro License — $49 USD</h1>
    </main>
  );
}

Pattern B — Custom UI with parity data (ParityProvider + useParityEdge)

Wrap your tree in <ParityProvider>, then call useParityEdge() in any child component to read the resolved data and build your own UI.

// app/layout.tsx  (or a shared client wrapper)
'use client';
import { ParityProvider } from 'parityedge/react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html><body>
      <ParityProvider projectId="prj_live_YOUR_PROJECT_ID">
        {children}
      </ParityProvider>
    </body></html>
  );
}

// app/pricing/page.tsx
'use client';
import { useParityEdge } from 'parityedge/react';

export default function PricingPage() {
  const { data } = useParityEdge();
  const basePrice = 49;
  const finalPrice = data?.eligible
    ? basePrice * (1 - data.discountPercentage / 100)
    : basePrice;

  return (
    <main className="max-w-4xl mx-auto py-12 px-4">
      <h1 className="text-3xl font-bold">Pro License — ${finalPrice.toFixed(2)} USD</h1>
      {data?.eligible && (
        <p className="text-emerald-400 mt-2 font-medium">
          {data.flag} Use code <strong>{data.couponCode}</strong> at checkout.
        </p>
      )}
    </main>
  );
}
Do not combine both patterns on the same page. Using <ParityBanner> and useParityEdge() together causes two separate API calls to the edge — wasting quota and doubling rate-limit exposure. Use Pattern A for banner-only, or Pattern B for custom UI. useParityEdge() must always be inside a <ParityProvider> tree.

Edge Middleware for Server-Side Pre-Rendering

Use createParityMiddleware from parityedge/next to resolve visitor parity at the edge and stamp x-parity-* headers on every matching request — zero client-side waterfall:

// middleware.ts
import { createParityMiddleware } from 'parityedge/next';

export default createParityMiddleware({
  projectId: process.env.NEXT_PUBLIC_PARITYEDGE_PROJECT_ID!,
  // timeoutMs: 3000, // optional — default 3 s, fails open on timeout
});

export const config = {
  matcher: ['/pricing', '/checkout/:path*'],
};

Then read the stamped headers in any Server Component, Route Handler, or Server Action:

// app/pricing/page.tsx (Server Component)
import { headers } from 'next/headers';
import { getParityHeaders } from 'parityedge/next';

export default async function PricingPage() {
  const parity = getParityHeaders(await headers());

  const basePrice = 49;
  const finalPrice = parity.isEligible
    ? basePrice * (1 - parity.discountPercentage / 100)
    : basePrice;

  return (
    <main>
      <h1>Pro License — ${finalPrice.toFixed(2)} USD</h1>
      {parity.isEligible && (
        <p>Use code <strong>{parity.couponCode}</strong> at checkout.</p>
      )}
    </main>
  );
}
Note: Do not use req.geo?.country — that is a Vercel-only deprecated field and does not call the ParityEdge API. Always use createParityMiddleware.