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.
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>
);
}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 Resolution using `useSWR`
For statically exported pages (next export or static JAMstack setups), fetch dynamically from the edge in the browser:
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,
};
}