Skip to content
IP IPBot
Get Started

Next.js Integration

This guide shows you how to integrate IPBot into your Next.js application for geolocation, security, and personalization.

Next.js offers multiple ways to integrate IPBot depending on your needs:

  • Client-side: For simple lookups and personalization
  • Server-side: For secure API calls and SSR
  • API Routes: For proxying and custom endpoints
  • Middleware: For edge-based redirects and blocking

No package installation is required - IPBot is a REST API. However, you may want to add a fetch helper:

Terminal window
# Optional: Install isomorphic-unfetch for better Node/browser compatibility
npm install isomorphic-unfetch
# or
yarn add isomorphic-unfetch

For client-side IP lookup and personalization:

utils/ipbot.ts
export interface IPBotResponse {
ip: string;
location: {
country: string;
country_code: string;
region: string;
city: string;
postal: string;
latitude: number;
longitude: number;
timezone: string;
};
network: {
asn: string;
org: string;
radar: "isp" | "datacenter" | "mobile" | "vpn" | "proxy" | "tor" | null;
};
security: {
risk_score: number;
risk_reasons: string[];
usage_type: string;
is_datacenter: boolean;
is_proxy: boolean;
threat_level: "Low" | "Medium" | "High";
threat_lists: string[];
};
telemetry: {
user_agent: string;
is_browser: boolean;
};
meta: {
process_time_us: number;
data_version: string;
engine: string;
};
}
export async function getIPInfo(ip?: string): Promise<IPBotResponse> {
const url = ip ? `https://api.ipbot.com/${ip}` : "https://api.ipbot.com/";
const response = await fetch(url);
if (!response.ok) {
throw new Error(`IPBot API error: ${response.statusText}`);
}
return response.json();
}

Create a custom hook for using IPBot in components:

hooks/useIPInfo.ts
import { useState, useEffect } from "react";
import { getIPInfo, IPBotResponse } from "@/utils/ipbot";
export function useIPInfo(ip?: string) {
const [data, setData] = useState<IPBotResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
getIPInfo(ip)
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [ip]);
return { data, loading, error };
}
components/LocationBanner.tsx
import { useIPInfo } from "@/hooks/useIPInfo";
export function LocationBanner() {
const { data, loading, error } = useIPInfo();
if (loading) return <div>Loading...</div>;
if (error) return null;
if (!data) return null;
return (
<div className="location-banner">
<p>
Detected location: {data.location.city}, {data.location.country}
</p>
{data.security.risk_score > 50 && (
<p className="warning">Connecting from a high-risk location</p>
)}
</div>
);
}

For server-side rendering with IP data:

// pages/index.tsx or app/page.tsx
import { GetServerSideProps } from 'next';
import { IPBotResponse } from '@/utils/ipbot';
interface HomeProps {
ipInfo: IPBotResponse | null;
ipError?: string;
}
export default function Home({ ipInfo, ipError }: HomeProps) {
if (ipError) {
return <div>Error loading location data</div>;
}
return (
<main>
{ipInfo && (
<div>
<h1>Welcome from {ipInfo.location.city}!</h1>
<p>Country: {ipInfo.location.country}</p>
<p>ISP: {ipInfo.network.org}</p>
</div>
)}
</main>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
// Get IP from headers (important for Vercel/CDN deployments)
const ip = getIPFromRequest(context.req);
try {
const response = await fetch(`https://api.ipbot.com/${ip}`);
const ipInfo = await response.json();
return {
props: {
ipInfo,
},
};
} catch (error) {
return {
props: {
ipInfo: null,
ipError: 'Failed to fetch IP info',
},
};
}
};
function getIPFromRequest(req: any): string {
// Check various headers for the real IP
return (
req.headers['x-forwarded-for']?.split(',')[0].trim() ||
req.headers['x-real-ip'] ||
req.headers['cf-connecting-ip'] ||
req.socket?.remoteAddress ||
'unknown'
);
}

Create a proxy API route to hide the IPBot endpoint and add caching:

pages/api/ip-info.ts
import type { NextApiRequest, NextApiResponse } from "next";
interface IPBotResponse {
ip: string;
location: any;
security: any;
// ... other fields
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
// Only allow GET requests
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const ip = req.query.ip as string | undefined;
try {
// Get client IP if not provided
const lookupIp = ip || getClientIP(req);
// Call IPBot API
const response = await fetch(`https://api.ipbot.com/${lookupIp}`);
const data: IPBotResponse = await response.json();
// Add CORS headers
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=3600",
);
return res.status(200).json(data);
} catch (error) {
console.error("IPBot API error:", error);
return res.status(500).json({ error: "Failed to fetch IP information" });
}
}
function getClientIP(req: NextApiRequest): string {
return (
(req.headers["x-forwarded-for"] as string)?.split(",")[0].trim() ||
(req.headers["x-real-ip"] as string) ||
(req.headers["cf-connecting-ip"] as string) ||
req.socket.remoteAddress ||
"unknown"
);
}

Method 4: Next.js Middleware (Edge Runtime)

Section titled “Method 4: Next.js Middleware (Edge Runtime)”

Use middleware for location-based redirects and blocking at the edge:

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const BLOCKED_COUNTRIES = ["CN", "RU", "KP"];
const MIN_RISK_SCORE = 70;
export async function middleware(request: NextRequest) {
const ip = getClientIP(request);
// Skip for static assets and API health checks
if (
request.nextUrl.pathname.startsWith("/_next") ||
request.nextUrl.pathname.startsWith("/api/health")
) {
return NextResponse.next();
}
try {
// Call IPBot API
const response = await fetch(`https://api.ipbot.com/${ip}`, {
// Use edge runtime compatible fetch options
});
if (!response.ok) {
return NextResponse.next();
}
const data = await response.json();
const countryCode = data.location?.country_code;
const riskScore = data.security?.risk_score;
// Block based on country
if (countryCode && BLOCKED_COUNTRIES.includes(countryCode)) {
return new Response("Access denied from your location", { status: 403 });
}
// Block based on risk score
if (riskScore && riskScore > MIN_RISK_SCORE) {
return new Response("Access denied due to security concerns", {
status: 403,
});
}
// Add location info to headers for use in pages
const responseHeaders = new Headers(request.headers);
responseHeaders.set("x-country", countryCode || "unknown");
responseHeaders.set("x-risk-score", riskScore?.toString() || "0");
return NextResponse.next({
request: {
headers: responseHeaders,
},
});
} catch (error) {
// On error, allow the request to proceed
return NextResponse.next();
}
}
function getClientIP(request: NextRequest): string {
return (
request.headers.get("cf-connecting-ip") ||
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
request.headers.get("x-real-ip") ||
"unknown"
);
}
// Configure which routes the middleware runs on
export const config = {
matcher: [
/*
* Match all request paths except:
* - api routes that handle their own auth
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api/health|_next/static|_next/image|favicon.ico).*)",
],
};

Method 5: App Router (Next.js 13+) Server Actions

Section titled “Method 5: App Router (Next.js 13+) Server Actions”

For the App Router, use Server Actions:

app/actions.ts
"use server";
import { IPBotResponse } from "@/utils/ipbot";
export async function getServerIPInfo(): Promise<IPBotResponse> {
const response = await fetch("https://api.ipbot.com/", {
cache: "no-store", // Or use Next.js revalidate
});
if (!response.ok) {
throw new Error("Failed to fetch IP info");
}
return response.json();
}
app/page.tsx
import { getServerIPInfo } from "./actions";
export default async function Home() {
const ipInfo = await getServerIPInfo();
return (
<main>
<h1>Welcome from {ipInfo.location.city}!</h1>
<p>Country: {ipInfo.location.country}</p>
</main>
);
}

Implement proper caching to minimize API calls:

app/actions.ts
export async function getIPInfoWithCache(ip: string) {
const response = await fetch(`https://api.ipbot.com/${ip}`, {
next: {
revalidate: 3600, // Revalidate every hour
tags: ["ip-info", `ip-${ip}`],
},
});
return response.json();
}
hooks/useIPInfo.ts
import { useQuery } from "@tanstack/react-query";
export function useIPInfo(ip?: string) {
return useQuery({
queryKey: ["ip-info", ip],
queryFn: () => getIPInfo(ip),
staleTime: 1000 * 60 * 60, // 1 hour
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
});
}

Vercel automatically sets the x-forwarded-for header:

const ip = req.headers["x-forwarded-for"] as string;

Cloudflare sets the cf-connecting-ip header:

const ip = req.headers["cf-connecting-ip"] as string;

For self-hosted Next.js behind a reverse proxy, configure proxy headers:

const ip = (req.headers["x-real-ip"] as string) || req.socket.remoteAddress;
components/LocalizedContent.tsx
"use client";
import { useIPInfo } from "@/hooks/useIPInfo";
export function LocalizedContent() {
const { data, loading } = useIPInfo();
if (loading) return <div>Loading...</div>;
const countryCode = data?.location?.country_code;
return (
<div>
{countryCode === "US" && (
<div>
<h2>Welcome to our US store!</h2>
<p>Free shipping on orders over $50.</p>
</div>
)}
{countryCode === "GB" && (
<div>
<h2>Welcome to our UK store!</h2>
<p>Free shipping on orders over 35.</p>
</div>
)}
{!["US", "GB"].includes(countryCode || "") && (
<div>
<h2>International shipping available</h2>
<p>We ship worldwide from our locations.</p>
</div>
)}
</div>
);
}

Solution: Next.js API routes handle CORS automatically. Use the proxy method above.

Solution: Check headers in order of priority for your hosting setup.

Solution: Implement proper caching and use Next.js revalidation.