Next.js Example
A Next.js 15 application with React 19 that renders a MapLibre vector tile map and adds places search. Uses App Router route handlers as a server-side token proxy.
Prerequisites
- Node.js 18+ (20 LTS recommended)
- A Geog API key (create one in the console)
Quick Start
git clone https://github.com/geogdev/examples.git
cd examples/frameworks/nextjs-maplibre
npm install
cp .env.local.example .env.local
Add your API key to .env.local:
GEOG_API_KEY=your-api-key-here
Start the dev server:
npm run dev
Open http://localhost:3000.
Project Structure
| File | Description |
|---|---|
app/api/geog-token/route.ts | Route handler — exchanges API key for short-lived token, caches at 80% TTL |
app/api/places/search/route.ts | Route handler — proxies places search requests to the Geog API |
lib/geog.ts | Typed API client with token exchange and places search functions |
app/components/Map.tsx | Client component — MapLibre map with transformRequest for auth |
app/components/PlacesSearch.tsx | Client component — search input, results list, and map markers |
app/page.tsx | Home page composing Map and PlacesSearch |
.env.local.example | Environment variable template |
How It Works
Server-Side Token Proxy
The API key stays server-side in .env.local. Next.js App Router route handlers act as the proxy:
// app/api/geog-token/route.ts
let cachedToken: TokenResponse | null = null;
let refreshAt: number = 0;
export async function GET() {
const now = Date.now();
if (cachedToken && now < refreshAt) {
return NextResponse.json({
access_token: cachedToken.access_token,
expires_in: cachedToken.expires_in,
});
}
cachedToken = await exchangeToken(process.env.GEOG_API_KEY!);
refreshAt = now + cachedToken.expires_in * 1000 * 0.8;
return NextResponse.json({
access_token: cachedToken.access_token,
expires_in: cachedToken.expires_in,
});
}
Client Components
Map and search components use the "use client" directive. The map component fetches a token from the local route handler, then uses MapLibre's transformRequest to inject the Bearer header:
// app/components/Map.tsx
"use client";
const mapRef = useRef<maplibregl.Map | null>(null);
const tokenRef = useRef<string>("");
useEffect(() => {
fetch("/api/geog-token")
.then((r) => r.json())
.then((data) => {
tokenRef.current = data.access_token;
mapRef.current = new maplibregl.Map({
transformRequest: (url) => {
if (url.startsWith("https://api.geog.dev")) {
return {
url,
headers: { Authorization: `Bearer ${tokenRef.current}` },
};
}
},
// ...
});
});
}, []);
Full Source
github.com/geogdev/examples/tree/main/frameworks/nextjs-maplibre
See Also
- Examples Overview — All examples and the integration pattern
- Token Exchange API — Endpoint specification
- Vector Tiles API — Tile endpoint reference
- Places API — Search endpoint reference
- Styles & Sprites — Hosted themes, sprites, and npm packages