Get the user's location with the Geolocation API in a Next.js + TypeScript app — typed coordinates, handling permission denials and errors, tuning accuracy, watching position changes, and a typed client-component hook.
Why: the Geolocation API asks the browser for the device's coordinates. It is permission-gated — the browser shows a prompt and the user must allow it. getCurrentPosition takes a success callback and an error callback; it is callback-based, not Promise-based. TypeScript already ships the GeolocationPosition / GeolocationPositionError types.
navigator.geolocation.getCurrentPosition(
(position: GeolocationPosition) => {
const { latitude, longitude } = position.coords
console.log(latitude, longitude)
},
(error: GeolocationPositionError) => {
console.log('Could not get location:', error.message)
},
)Note: always handle the error callback — the user may deny permission, the device may have no GPS, or the request may time out. The error code tells you which: 1 = permission denied, 2 = position unavailable, 3 = timeout.
navigator.geolocation.getCurrentPosition(
(pos) => console.log(pos.coords.latitude, pos.coords.longitude),
(error) => {
if (error.code === error.PERMISSION_DENIED) {
console.log('User said no — fall back to a manual location input')
} else {
console.log('Location error:', error.message)
}
},
)When: a third argument tunes the request. enableHighAccuracy asks for GPS-level precision (and uses more battery), timeout caps how long to wait, and maximumAge lets you accept a recently cached position instead of measuring again. The options object has the built-in PositionOptions type.
const options: PositionOptions = {
enableHighAccuracy: true, // GPS-level precision (uses more battery)
timeout: 10000, // give up after 10s
maximumAge: 60000, // a position up to 1 min old is fine
}
navigator.geolocation.getCurrentPosition(onSuccess, onError, options)Why: for a map that follows the user, watchPosition fires the callback every time the location changes. It returns a numeric id — pass it to clearWatch when you are done, or it keeps running and draining the battery.
const watchId: number = navigator.geolocation.watchPosition((pos) => {
console.log('Moved to', pos.coords.latitude, pos.coords.longitude)
})
// stop watching
navigator.geolocation.clearWatch(watchId)Note: navigator exists only in the browser, so this must be a "use client" component. Type the state, start the watch inside useEffect, and return a cleanup function that clears it when the component unmounts — otherwise the watch leaks.
'use client'
import { useState, useEffect } from 'react'
type Coords = { lat: number; lng: number }
export function useLocation() {
const [coords, setCoords] = useState<Coords | null>(null)
useEffect(() => {
const id = navigator.geolocation.watchPosition((pos) =>
setCoords({ lat: pos.coords.latitude, lng: pos.coords.longitude }),
)
return () => navigator.geolocation.clearWatch(id) // cleanup on unmount
}, [])
return coords
}