Skip to main content
ParityEdge
Next.js Pages Router
Framework Integration Guide

Next.js Pages Router (pages/ Directory)

Complete guide for integrating ParityEdge into Next.js 12, 13, and 14 applications using the classic Pages Router, including near-zero CLS server-side rendering via getServerSideProps, custom _app.tsx scripts, and client-side hooks.

Pre-Hydrated Server-Side Rendering (Near-Zero CLS)

Server-Side Parity Resolution in getServerSideProps

⚠ Important: Server-Side vs Client-Side Geo-Resolution

When your Next.js server (hosted on Vercel, AWS, Railway, etc.) calls the ParityEdge API, the geo-resolution uses the server's datacenter IP address — not your visitor's IP. This means all server-side calls will resolve to the server's location (usually a US datacenter) regardless of where the real visitor is located.

Recommended approach for Pages Router: Use the client-side hook (useParityEdge) shown in the section below, or the global embed script in _app.tsx. Both resolve directly from the visitor's browser IP with zero server round-trips.

The server-side pattern below is only useful when your hosting platform injects the real visitor IP into a trusted request header that you can read server-side (e.g., x-real-ip from a self-managed reverse proxy). In that case, pass it as shown — but verify your platform actually sets this header before visitor geo is correct.

If your platform provides a trusted real visitor IP via headers, you can pre-render the localized price server-side for near-zero Cumulative Layout Shift (<0.01 CLS):

// pages/pricing.tsx
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import Head from 'next/head';

interface ParityData {
  eligible: boolean;
  countryCode: string;
  countryName: string;
  discountPercentage: number;
  couponCode: string;
  currency: string;
  isVpn: boolean;
}

export const getServerSideProps: GetServerSideProps<{
  parity: ParityData | null;
}> = async (context) => {
  const rawIp = context.req.headers['x-forwarded-for'] || context.req.socket.remoteAddress;
  const clientIp = Array.isArray(rawIp) ? rawIp[0] : rawIp || '';

  try {
    const res = await fetch(
      'https://api.parityedge.app/v1/lookup?pid=prj_live_YOUR_PROJECT_ID',
      {
        headers: {
          'x-forwarded-for': clientIp,
        },
      }
    );

    if (!res.ok) throw new Error(`Edge API responded with ${res.status}`);
    const parity: ParityData = await res.json();

    return {
      props: {
        parity,
      },
    };
  } catch (error) {
    console.warn('[ParityEdge] Fallback to default USD pricing:', error);
    return {
      props: {
        parity: null,
      },
    };
  }
};

export default function PricingPage({
  parity,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  const baseUsdPrice = 49;
  const finalPrice = parity?.eligible
    ? baseUsdPrice * (1 - parity.discountPercentage / 100)
    : baseUsdPrice;

  return (
    <div className="min-h-screen bg-slate-950 text-white p-8">
      <Head>
        <title>Pricing | My SaaS</title>
      </Head>

      <div className="max-w-md mx-auto p-8 rounded-2xl bg-slate-900 border border-slate-800">
        <h2 className="text-xl font-bold">Pro Subscription</h2>
        
        {/* Pre-rendered Localized Price (Near-Zero CLS) */}
        <div className="text-4xl font-extrabold mt-3 font-mono">
          ${finalPrice.toFixed(2)} USD <span className="text-sm font-normal text-slate-400">/ mo</span>
        </div>

        {parity?.eligible && (
          <div className="mt-4 p-3 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs">
            <p className="font-bold">
              {parity.discountPercentage}% Parity Discount Active for {parity.countryName}!
            </p>
            <p className="mt-1 text-slate-300">
              Use code <span className="font-mono font-bold text-white">{parity.couponCode}</span> at checkout.
            </p>
          </div>
        )}

        <button className="w-full mt-6 py-3 rounded-xl bg-emerald-600 font-bold hover:bg-emerald-500 transition-colors">
          Get Started
        </button>
      </div>
    </div>
  );
}
Global Embed Script in `_app.tsx`

Mount Global Edge Script with `next/script`

If you prefer a drop-in floating banner without modifying individual page props, add the CDN script to pages/_app.tsx using Next.js Script optimization:

// pages/_app.tsx
import type { AppProps } from 'next/app';
import Script from 'next/script';
import '@/styles/globals.css';

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      {/* High-Performance Edge Script (Non-blocking) */}
      <Script
        src="https://api.parityedge.app/v1/parity.global.js"
        data-project-id="prj_live_YOUR_PROJECT_ID"
        data-theme="dark"
        data-position="bottom-pill"
        strategy="afterInteractive"
      />
      <Component {...pageProps} />
    </>
  );
}
Client-Side Hook with SWR

Client-Side Resolution using `useSWR`

For statically exported pages (next export or static JAMstack setups), fetch dynamically from the edge in the browser:

Domain whitelist required for client-side fetches. This SWR hook runs in the browser and sends an Origin header to the ParityEdge API — your domain must be whitelisted in the project Dashboard. The getServerSideProps pattern above sends no Origin header (server-to-server) and bypasses the whitelist check. If you use both patterns on the same project, a missing whitelist entry will block the client-side hook while the server-side call succeeds — causing inconsistent behaviour with no clear error.
import useSWR from 'swr';

const fetcher = (url: string) =>
  fetch(url, { credentials: 'omit' }).then((res) => {
    if (!res.ok) throw new Error(`ParityEdge: HTTP ${res.status}`);
    return res.json();
  });

export function useClientParity(projectId: string) {
  const { data, error, isLoading } = useSWR(
    `https://api.parityedge.app/v1/lookup?pid=${encodeURIComponent(projectId)}`,
    fetcher,
    { revalidateOnFocus: false, dedupingInterval: 300000 }
  );

  if (error) console.warn('[ParityEdge] lookup failed, defaulting to base price:', error.message);

  return {
    parity: data ?? null,
    isLoading,
    isError: !!error,
  };
}