Jason JunJason Jun

Geolocation detection with Vercel Edge Functions

9 July 2024

For the latest rebuild of my personal website, I wanted to add some data about the visitors. Simply counting visits felt too basic and wasn't interesting at all. Then I discovered Rauno Freiberg's website (https://rauno.me), which displays a 'last visit from ...' message on the home page. I was curious about how he implemented this feature without requesting geolocation access via the browser API. This led me to explore alternative methods, such as using server-side geolocation based on IP addresses with Vercel's edge functions.

Vercel Edge Functions

Vercel provides a geolocation helper function with the @vercel/edge package, which allows you to detect basic geolocation data of visitors without using cookies or other tracking mechanisms.

This function uses geolocation headers included in all Vercel deployments to access location information such as country, region, and city based on the visitor's IP address. This geolocation data can be used within Vercel Edge Functions or Edge Middleware to personalise content or implement location-specific logic​.

Create API route

For my case, I don't need the data in middleware so I created an API route instead. The endpoint returns city and country data of the visitor.

// /api/geolocation
import { NextRequest, NextResponse } from 'next/server'
import { geolocation } from '@vercel/edge'
export async function GET(request: NextRequest) {
  try {
    const { city, country } = geolocation(request) || {}
    return NextResponse.json({ city, country })
  } catch (error) {
    console.error('Geolocation error:', error)
    return NextResponse.json(
      { error: 'Unable to retrieve geolocation data' },
      { status: 500 }

Store data in Supabase

I created a table in Supabase to store the geolocation data of the visitors. The table has two columns: city and country.

const addGeoToSupabase = async (city: string, country: string) => {
  const { data, error } = await supabase
    .insert({ city, country })
  if (error) throw error
  return data

In order to avoid unnecessary API calls, I used the sessionStorage to store a flag that indicates whether the geolocation data has already been fetched. If the flag is not set, the data is fetched and stored in Supabase.

useEffect(() => {
  const footprint = sessionStorage.getItem('footprint')
  const fetchGeo = async () => {
    const res = await fetch('/api/geo')
    const { city, country } = await res.json()
    if (city && country) await addGeoToSupabase(city, country)
  if (footprint !== 'true') {
    sessionStorage.setItem('footprint', 'true')
}, [])

Display data on the website

For displaying the data on the website, I used a tooltip component that I built based on Radix UI. I want to visualise the visit count based on locations, but for now I've just listed the cities grouped by countries.


One tricky part was that the country data from Vercel's geolocation function is in country codes, not country names. I had to map the country codes to country names. I used ChatGPT to generate the mapping data based on this webpage: IBAN Country Codes. ChatGPT is very useful for generating or converting data into a structured format.