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 react-canvas-globe canvas-globe react
react-canvas-globe is the official React package. It uses the same renderer,
options, types, and commercial license as canvas-globe.
The component
import { CanvasGlobe } from "react-canvas-globe";
export function Visitors({ markers }) {
return <CanvasGlobe 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 { CanvasGlobe } from "react-canvas-globe";
export function Explorer({ markers }) {
const globe = useRef(null);
return (
<>
<CanvasGlobe
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.
<CanvasGlobe 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]);
<CanvasGlobe 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 { CanvasGlobe } from "react-canvas-globe";
Or load it lazily to keep it out of the server bundle entirely:
import dynamic from "next/dynamic";
const Globe = dynamic(
() => import("react-canvas-globe").then((m) => m.CanvasGlobe),
{ ssr: false, loading: () => <div style={{ aspectRatio: 1 }} /> },
);
TypeScript
import { useRef } from "react";
import { CanvasGlobe } from "react-canvas-globe";
import type { GeoGlobe, Marker } from "canvas-globe";
const globe = useRef<GeoGlobe | null>(null);
const markers: Marker[] = [{ lat: 23.03, lon: 72.58, count: 12 }];
<CanvasGlobe ref={globe} markers={markers} />;
A complete example
"use client";
import { useMemo, useRef, useState } from "react";
import { CanvasGlobe } from "react-canvas-globe";
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>
<CanvasGlobe
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>
);
}