Primitives to query geolocation and observe changes.
| Stage | Category | Version | Last Updated | Demo |
|---|---|---|---|---|
| 3 | Browser APIs | 3.0.0-next.2 (next) | Aug 12, 2026 | Demo → |
npm i @solid-primitives/geolocation@nextPrimitives to query and watch geolocation information from within the browser.
makeGeolocation
A non-reactive one-shot query. No Solid owner required — can be used outside components. Returns a [query, cleanup] tuple.
const [query, cleanup] = makeGeolocation({ enableHighAccuracy: true });const coords = await query();cleanup();Definition
makeGeolocation( options?: PositionOptions): [query: () => Promise<GeolocationCoordinates>, cleanup: VoidFunction]makeGeolocationWatcher
A non-reactive continuous watcher. No Solid owner required. Returns a [store, cleanup] tuple.
const [store, cleanup] = makeGeolocationWatcher();console.log(store.location); // GeolocationCoordinates | nullconsole.log(store.error); // GeolocationPositionError | nullcleanup();Definition
makeGeolocationWatcher( options?: PositionOptions): [ store: { location: GeolocationCoordinates | null; error: GeolocationPositionError | null }, cleanup: VoidFunction]createGeolocation
A reactive one-shot query. Returns an async accessor that integrates with <Loading> boundaries — the component subtree suspends until the position resolves. Re-queries automatically when reactive options change, or manually via refetch().
const [location, refetch] = createGeolocation();// Suspends until first fix:<Loading fallback="Locating..."> <div>{location().latitude}, {location().longitude}</div></Loading>
// Show a subtle indicator while re-querying in the background:<Show when={isPending(() => location())}>Updating position...</Show>With reactive options:
const [opts, setOpts] = createSignal<PositionOptions>({ enableHighAccuracy: false });const [location, refetch] = createGeolocation(opts);// Automatically re-queries when opts() changesWith a server-side seed (e.g. IP geolocation from Cloudflare):
// On the server, location() resolves immediately with the seed instead of throwing NotReadyError.// On the client, the seed is ignored and GPS is queried directly.const [location, refetch] = createGeolocation(undefined, { latitude: cf.latitude, longitude: cf.longitude,});Definition
createGeolocation( options?: MaybeAccessor<PositionOptions>, initialLocation?: GeolocationCoord): [location: () => Promise<GeolocationCoordinates>, refetch: VoidFunction]createGeolocationWatcher
A reactive continuous watcher. location throws NotReadyError (integrating with <Loading>) until the first GPS fix, then updates reactively without re-suspending. error is a signal accessor for recoverable in-component error handling. The watcher starts and stops reactively based on enabled. Reactive options restarts the watcher when the enabled state is active.
const [enabled, setEnabled] = createSignal(true);const { location, error } = createGeolocationWatcher(enabled);// Show error inline (recoverable — no error boundary needed):<Show when={error()}> Permission denied — <button onClick={retry}>retry</button></Show>
// Suspends until first GPS fix, then updates live:<Loading fallback="Acquiring GPS fix..."> <Map lat={location().latitude} lng={location().longitude} /></Loading>With a server-side seed (e.g. IP geolocation from Cloudflare):
// On the server, location() returns the seed immediately — no NotReadyError, no <Loading> flash.// On the client, the seed is the initial signal value; real GPS coordinates replace it as soon// as the watcher fires.const { location, error } = createGeolocationWatcher(true, undefined, { latitude: cf.latitude, longitude: cf.longitude,});Non-lat/lng fields (accuracy, altitude, heading, speed) are set to 0 or null on a seeded value. They are replaced by real values once GPS fires on the client.
Definition
createGeolocationWatcher( enabled: MaybeAccessor<boolean>, options?: MaybeAccessor<PositionOptions>, initialLocation?: GeolocationCoord): { location: Accessor<GeolocationCoordinates>; error: Accessor<GeolocationPositionError | null>;}createDistance
Reactively calculates the distance from the user's current GPS location to a target coordinate using the Haversine formula. Returns null until the first GPS fix arrives.
const distance = createDistance({ latitude: 48.8566, longitude: 2.3522 });<Show when={distance() !== null} fallback="Locating..."> {distance()!.toFixed(1)} km from the Eiffel Tower</Show>With a reactive target and metre units:
const [target, setTarget] = createSignal({ latitude: 48.8566, longitude: 2.3522 });const distance = createDistance(target, { unit: "m" });Definition
createDistance( target: MaybeAccessor<GeolocationCoord>, options?: { unit?: "km" | "m"; // default "km" enabled?: MaybeAccessor<boolean>; watcherOptions?: MaybeAccessor<PositionOptions>; initialLocation?: GeolocationCoord; }): Accessor<number | null>createWithinRadius
Reactively tracks whether the user's GPS location is within a given radius (in metres) of a centre coordinate. Returns false until the first GPS fix arrives.
const nearby = createWithinRadius({ latitude: 48.8566, longitude: 2.3522 }, 500);<Show when={nearby()}>You are near the Eiffel Tower!</Show>With a reactive radius:
const [radius, setRadius] = createSignal(500);const nearby = createWithinRadius({ latitude: 48.8566, longitude: 2.3522 }, radius);Definition
createWithinRadius( center: MaybeAccessor<GeolocationCoord>, radius: MaybeAccessor<number>, // in metres options?: { enabled?: MaybeAccessor<boolean>; watcherOptions?: MaybeAccessor<PositionOptions>; initialLocation?: GeolocationCoord; }): Accessor<boolean>SSR / Server-side initial location
By default all reactive primitives throw NotReadyError on the server, which integrates with <Loading> boundaries to show a fallback during SSR. If you can supply approximate coordinates server-side (for example from Cloudflare's cf.latitude / cf.longitude request headers, or any other IP geolocation service), you can pass them as initialLocation to skip the loading state entirely.
// SolidStart server loader exampleexport const route = { load: async ({ request }) => { const lat = Number(request.headers.get("cf-iplatitude")); const lng = Number(request.headers.get("cf-iplongitude")); return { ipCoords: { latitude: lat, longitude: lng } }; },};
// Componentconst data = useRouteData<typeof route.load>();
// Server: renders immediately with IP coords — no <Loading> flash.// Client: IP coords are the initial signal value; real GPS replaces them on the first fix.const { location } = createGeolocationWatcher(true, undefined, data()?.ipCoords);When a seed is provided:
- Server —
location()/distance()/within()return values derived from the seed instead of throwing or returningnull/false. - Client — the seed is the starting value; the GPS watcher overwrites it as soon as the first fix arrives. No
<Loading>suspension occurs if the seed is present. - Fields beyond
latitude/longitude(accuracy,altitude,heading,speed) are0ornulluntil real GPS data arrives.
Types
type GeolocationCoord = { latitude: number; longitude: number };interface GeolocationCoordinates { readonly accuracy: number; readonly altitude: number | null; readonly altitudeAccuracy: number | null; readonly heading: number | null; readonly latitude: number; readonly longitude: number; readonly speed: number | null;}Default position options (overridden by anything you pass):
const geolocationDefaults: PositionOptions = { enableHighAccuracy: false, maximumAge: 0, timeout: Number.POSITIVE_INFINITY,};Changelog
See CHANGELOG.md