@ipgeotrace/next
v0.1.0
Published
Next.js helpers for IPGeoTrace. Resolve the caller's location in Route Handlers, Server Components, and edge middleware.
Maintainers
Readme
@ipgeotrace/next
Next.js helpers for IPGeoTrace. Resolve the
caller's location in Route Handlers, Server Components, and edge middleware. Built on
@ipgeotrace/client — the secret key stays server-side.
Sign up and grab your API key at ipgeotrace.com.
Works on the Node and edge runtimes (the core client is fetch-based and dependency-free). App
Router (Next 13+).
Client Components (
'use client') can't use the secret key. To read the visitor's location in the browser, use@ipgeotrace/browser(or the upcoming@ipgeotrace/reacthook) with a publishable key.
Install
npm add @ipgeotrace/nextCreate the resolver once and reuse it:
// lib/geo.ts
import { createGeo } from '@ipgeotrace/next';
export const geo = createGeo({
apiKey: process.env.IPGEOTRACE_API_KEY!,
clientOptions: { cache: true },
});Route Handler
// app/api/geo/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { geo } from '@/lib/geo';
export async function GET(request: NextRequest) {
const lookup = await geo.resolve(request);
return NextResponse.json(lookup);
}Server Component
headers() from next/headers is a valid source:
// app/page.tsx
import { headers } from 'next/headers';
import { geo } from '@/lib/geo';
export default async function Page() {
const lookup = await geo.resolve(await headers());
const currency = lookup.status === 'resolved' ? lookup.value?.country?.currency ?? 'USD' : 'USD';
return <PriceList currency={currency} />;
}resolve() returns a GeoLookup whose status is resolved | skipped | failed |
not_attempted. Private, loopback, and link-local callers are skipped locally with no API call.
Edge middleware — resolve once, read everywhere
Resolve at the edge and pass the result downstream as a request header so Server Components and Route Handlers reuse it instead of each calling the API:
// middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
import { geo } from '@/lib/geo';
import { GEO_HEADER, encodeGeoHeader } from '@ipgeotrace/next';
export async function middleware(request: NextRequest) {
const lookup = await geo.resolve(request);
const headers = new Headers(request.headers);
headers.set(GEO_HEADER, encodeGeoHeader(lookup));
return NextResponse.next({ request: { headers } });
}
export const config = { matcher: ['/((?!_next|favicon.ico).*)'] };Then read it anywhere without another lookup:
import { headers } from 'next/headers';
import { readGeoHeader } from '@ipgeotrace/next';
const lookup = readGeoHeader(await headers());Choosing the caller's IP
By default getCallerIp reads x-forwarded-for (first entry), then x-real-ip, then a platform
ip field if present. Override per resolver:
export const geo = createGeo({
apiKey: process.env.IPGEOTRACE_API_KEY!,
ipSelector: (source) => {
const headers = source instanceof Headers ? source : source.headers;
return headers.get('cf-connecting-ip') ?? undefined;
},
});See the @ipgeotrace/client README for caching, retries, and timeouts.
