React globe component
CanvasGlobe provides a React component for interactive globes and flat world maps without Three.js, WebGL, map tiles, or an API key.
npm install canvas-globe react
React is an optional peer dependency: only the /react entry point needs it.
The component
import { Globe } from "canvas-globe/react";
export function Visitors({ markers }) {
return <Globe markers={markers} preset="hologram" tooltip />;
}
Every option is a prop. The component diffs them shallowly on each render and calls
setOptions with only what changed, so you can pass inline objects freely.
Sizing
The canvas defaults to width: 100% with an aspectRatio derived from the mode and projection.
Override with style:
<Globe mode="map" style={{ maxWidth: 720, aspectRatio: "360 / 139" }} />
Imperative calls
The forwarded ref is the GeoGlobe instance.
import { useRef } from "react";
import { Globe } from "canvas-globe/react";
export function Explorer({ markers }) {
const globe = useRef(null);
return (
<>
<Globe
ref={globe}
markers={markers}
tooltip
onClick={(m) => globe.current.flyTo(m.lon, m.lat, { zoom: 3 })}
/>
<button onClick={() => globe.current.fitToMarkers()}>Fit all</button>
<button onClick={() => globe.current.exportImage({ preset: "og" })}>Export</button>
</>
);
}
Callbacks are always fresh
onHover, onClick, onCountryHover, onCountryClick and onRender are read through a ref, so
they never need to be memoised and never cause a rebuild.
<Globe onClick={(m) => setSelected(m)} />
Keeping expensive props stable
Options are diffed by identity. A new array every render means setOptions fires every render.
const arcs = useMemo(() => routes.map(toArc), [routes]);
<Globe markers={markers} arcs={arcs} />;
This matters most for arcs, whose great-circle points are cached per object: see
Performance.
Cleanup
The component destroys its instance on unmount. Nothing to do.
Next.js and SSR
The modules are safe to import on the server because they do not touch the DOM until construction. The canvas still needs a browser. In the App Router, mark the file:
"use client";
import { Globe } from "canvas-globe/react";
Or load it lazily to keep it out of the server bundle entirely:
import dynamic from "next/dynamic";
const Globe = dynamic(
() => import("canvas-globe/react").then((m) => m.Globe),
{ ssr: false, loading: () => <div style={{ aspectRatio: 1 }} /> },
);
TypeScript
import { useRef } from "react";
import { Globe } from "canvas-globe/react";
import type { GeoGlobe, Marker } from "canvas-globe";
const globe = useRef<GeoGlobe | null>(null);
const markers: Marker[] = [{ lat: 23.03, lon: 72.58, count: 12 }];
<Globe ref={globe} markers={markers} />;
A complete example
"use client";
import { useMemo, useRef, useState } from "react";
import { Globe } from "canvas-globe/react";
import { fromCSV } from "canvas-globe";
export function SignupMap({ csv }) {
const globe = useRef(null);
const [selected, setSelected] = useState(null);
const markers = useMemo(() => fromCSV(csv), [csv]);
return (
<figure>
<Globe
ref={globe}
scene="signups"
markers={markers}
counter={{ value: markers.length, label: "customers" }}
tooltip={(m) => `${m.label}: ${m.count}`}
onClick={(m) => {
setSelected(m);
globe.current.flyTo(m.lon, m.lat, { zoom: 3 });
}}
style={{ maxWidth: 560 }}
/>
<figcaption>{selected ? `${selected.label}: ${selected.count}` : "Click a city"}</figcaption>
</figure>
);
}