mapcn

shadcn/ui 互換 React マップコンポーネントライブラリ — MapLibre GL ベース

1 — 概要

mapcn は MapLibre GL をレンダリングエンジンに持つ、shadcn/ui 互換のマップコンポーネントライブラリ。コアコンポーネント全体が map.tsx という 1 ファイルに集約されており、npx shadcn add でプロジェクトにコピーして自由に改変できる。ライト/ダークテーマ自動切替・マーカー・ポップアップ・ルート・クラスタリングをゼロコンフィグで使えるのが売り。

1 fileコアの集約
shadcnCLI 対応
13コンポーネント数
7 blocksデモブロック

Next.js 16 (App Router) + TypeScript 5 + Tailwind CSS 4 で構築されたドキュメントサイトも同梱。shadcn レジストリスキーマに準拠した registry.json を公開しており、public/r/ にビルド済み JSON が配置される。

shadcn 哲学との一致: コンポーネントをパッケージとしてインストールするのではなく、ソースコードをプロジェクトにコピーする。components/ui/map.tsx として手元に置き、自由に改変できる。依存は maplibre-gl と lucide-react のみ。
ベースマップ利用条件: デフォルトの Carto ベースマップ(OpenStreetMap 由来)を商用利用する場合は CARTO Enterprise ライセンスが必要。非商用は CARTO grantee 向け条件に従う。styles prop や blank prop で OpenStreetMap タイル・MapTiler 等へ差し替え可能。
2 — インストール

shadcn が初期化済みのプロジェクトで以下を実行する。

# mapcn の map コンポーネントをプロジェクトに追加
npx shadcn add https://mapcn.dev/r/map.json

# インストール後の配置先
# → components/ui/map.tsx

依存パッケージの maplibre-gl も自動でインストールされる(shadcn の dependencies フィールドから解決)。

npm run dev              # 開発サーバー起動(Next.js)
npm run build            # Next.js プロダクションビルド
npm run registry:build   # public/r/ へレジストリ JSON を生成
                         # → shadcn build -o ./public/r
                         # → fix-registry-imports.ts でパス書き換え

registry:build は npx shadcn build -o ./public/r のあと、scripts/fix-registry-imports.ts で @/registry/map → @/components/ui/map へ import パスを書き換える。

3 — アーキテクチャ

マップインスタンスは React Context で全子コンポーネントに共有される。マーカーは MapLibre の DOM 要素を React Portal のマウント先として使うことで、宣言的 JSX と MapLibre のネイティブ API を統合している。

flowchart TD A["MapLibre GL
(レンダリングエンジン)"] --> B["Map
(Context Provider)"] B --> C["MapMarker
(Portal)"] B --> D["MapControls
(ズーム/コンパス)"] B --> E["MapRoute
(GeoJSON LineString)"] B --> E2["MapArc
(曲線アーク)"] B --> E3["MapGeoJSON
(GeoJSON レイヤー)"] B --> F["MapClusterLayer
(ポイントクラスター)"] B --> G["MapPopup
(独立ポップアップ)"] C --> C1["MarkerContent"] C --> C2["MarkerPopup"] C --> C3["MarkerTooltip"] C --> C4["MarkerLabel"] style A fill:#0891b2,color:#fff,stroke:none style B fill:#0369a1,color:#fff,stroke:none style C fill:#0e7490,color:#fff,stroke:none style D fill:#155e75,color:#fff,stroke:none style E fill:#164e63,color:#fff,stroke:none style E2 fill:#134e4a,color:#fff,stroke:none style E3 fill:#115e59,color:#fff,stroke:none style F fill:#083344,color:#fff,stroke:none style G fill:#0c4a6e,color:#fff,stroke:none style C1 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec style C2 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec style C3 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec style C4 fill:#e2f3f7,color:#0369a1,stroke:#b3e0ec
flowchart LR R["registry.json
(定義)"] --> B["npx shadcn build"] B --> J["public/r/*.json
(ビルド済み)"] J --> F["fix-registry-imports.ts
(パス書き換え)"] F --> U["ユーザープロジェクト
components/ui/map.tsx"] style R fill:#92400e,color:#fff,stroke:none style B fill:#b45309,color:#fff,stroke:none style J fill:#d97706,color:#fff,stroke:none style F fill:#ca8a04,color:#fff,stroke:none style U fill:#a16207,color:#fff,stroke:none
  • Single File Component: 全コンポーネントを map.tsx 1 ファイルに集約。shadcn のコピペ哲学と一致し依存を最小化。
  • React Portal でのマーカーレンダリング: MapLibre の DOM 要素を Portal のマウント先として使い、仮想 DOM とネイティブ API を統合。
  • Controlled / Uncontrolled 両対応: viewport + onViewportChange でコントロールドモード。internalUpdateRef フラグで無限ループを防止。
  • テーマ自動検出: MutationObserver で document.documentElement の class 変更を監視し、next-themes などと自動連携。
  • スタイルロード競合対策: isStyleLoaded を別フラグで管理し、100ms のバッファでレースコンディションを防止。
  • blank ベースマップ: blank prop でタイルなし透明キャンバスを使い、choropleth やアークなどデータ可視化専用レイアウトを構築。
  • MapGeoJSON / MapArc: GeoJSON ソース・レイヤーを宣言的に追加。アークは二次ベジェ曲線で日付変更線をまたぐ経路も短い方へ描画。
4 — コンポーネント一覧

すべて 1 ファイルに収録。インストール後は components/ui/map.tsx から import する。

Map
MapLibre GL のラッパー兼 Context Provider。forwardRef で MapRef(生の MapLibre インスタンス)を公開。すべての子コンポーネントはこの中に置く。
useMap
Map コンテキストから mapInstance と isLoaded を取得するフック。カスタムレイヤーや直接 API 操作に使う。
MapMarker
緯度経度を指定してマーカーを配置。ドラッグ対応。React Portal で DOM を管理するため、子コンポーネントを自由に置ける。
MarkerContent
マーカーの外見をカスタマイズする子コンポーネント。任意の React ノードを渡せる。
MarkerPopup
マーカーをクリックしたときに開くポップアップ。内容は任意の JSX。
MarkerTooltip
マーカーをホバーしたときに表示されるツールチップ。
MarkerLabel
マーカーの上下に表示されるテキストラベル。
MapPopup
マーカーに依存しない独立したポップアップ。任意の座標に配置できる。
MapControls
ズーム・コンパス・現在地・フルスクリーンのコントロール群。showZoom / showLocate などの props でオン/オフを切り替える。
MapRoute
GeoJSON LineString として座標配列からルートを描画。色・太さ・透明度・ダッシュ・クリック/ホバーイベントに対応。
MapClusterLayer
GeoJSON データ(URL または FeatureCollection)を受け取り、ポイントをクラスタリングして可視化。ジェネリクスでプロパティ型を保持。
MapArc
2 点間を曲線アークで接続。curvature で曲率、hoverPaint でホバー強調。日付変更線をまたぐ経路は短い方へ描画。
MapGeoJSON
GeoJSON ソースと fill/line レイヤーを宣言的に追加。blank マップ上の choropleth やカスタムデータレイヤー向け。クリック・ホバーイベント対応。
Prop型説明
theme"light" | "dark"テーマを強制指定。省略時はシステム設定を自動検出。
styles{ light?, dark? }カスタムタイルスタイル URL。デフォルトは Carto Positron / Dark Matter。
blankbooleanタイルなし透明ベースマップ。MapGeoJSON / MapArc などデータ可視化向け。styles 指定時は無視。
projectionProjectionSpecification{ type: "globe" } で 3D 地球儀モード。
viewportPartial<MapViewport>コントロールドモード用ビューポート(center, zoom, bearing, pitch)。
onViewportChange(v: MapViewport) => voidビューポート変更時のコールバック。
loadingbooleanローディングスピナーの表示制御。
5 — 使い方
import { Map, MapMarker, MarkerContent, MapControls } from "@/components/ui/map";

export function BasicMap() {
  return (
    <Map className="h-[400px] w-full" zoom={10} center={[139.69, 35.68]}>
      <MapMarker longitude={139.69} latitude={35.68}>
        <MarkerContent>
          <div className="rounded-full bg-primary text-primary-foreground p-1 text-xs">
            東京
          </div>
        </MarkerContent>
      </MapMarker>
      <MapControls showZoom showLocate />
    </Map>
  );
}
import { useState } from "react";
import { Map, MapViewport } from "@/components/ui/map";

export function ControlledMap() {
  const [viewport, setViewport] = useState<MapViewport>({
    center: [139.69, 35.68],
    zoom: 10,
    bearing: 0,
    pitch: 0,
  });

  return (
    <Map
      className="h-[400px] w-full"
      viewport={viewport}
      onViewportChange={setViewport}
    />
  );
}
import { Map, MapRoute, useMap } from "@/components/ui/map";

function FlyToButton() {
  const { mapInstance } = useMap();
  const handleFly = () => {
    mapInstance?.flyTo({ center: [135.5, 34.69], zoom: 12, duration: 1500 });
  };
  return <button onClick={handleFly}>大阪へ飛ぶ</button>;
}

export function RouteMap() {
  const coords: [number, number][] = [
    [139.69, 35.68], [137.00, 35.30], [135.50, 34.69],
  ];
  return (
    <Map className="h-[400px] w-full" zoom={7} center={[137.5, 35.4]}>
      <MapRoute coordinates={coords} color="#0891b2" width={3} />
      <FlyToButton />
    </Map>
  );
}
6 — ブロック

shadcn レジストリに登録されたブロック(完全なページコンポーネント)。npx shadcn add でプロジェクトに追加できる。

Analytics Map
analytics-map · iframe 940px
世界地図とリアルタイムアナリティクスダッシュボード。Recharts 連携でリージョン別グラフとデバイス統計を表示。overview-card + breakdown-card の 2 カードレイアウト。
Choropleth
choropleth · iframe 720px
国別メトリクスで色分けする世界 choropleth マップ。ホバーツールチップと凡例付き。use-world-data で GeoJSON を取得。
Uptime Monitor
uptime-monitor · iframe 720px
ステータスページ風のエッジネットワークマップ。拠点ごとのヘルス・レイテンシ・アップタイムをライブ表示。サイドバーで詳細確認。
Logistics Network
logistics-network · iframe 800px
国内物流ルートマップ+統計サイドバー。GeoJSON の輸送経路(アーク)をフィルタで切り替え。
Store Locator
store-locator · iframe 800px
検索可能な店舗リストとマップマーカーを双方向同期。サイドバーで店舗選択するとマップがフォーカス。
Heatmap
heatmap · iframe 800px
地震データの地球儀ヒートマップ。projection: "globe" で 3D 地球儀モード。ズーム依存のスタイリングで密度を可視化。
Delivery Tracker
delivery-tracker · iframe 720px
ライブ注文追跡。ルート進捗・配達員位置・注文詳細パネルを一体化したデモ実装。
  • mapcn.dev — 公式ドキュメントサイト(コンポーネント API・ブロックプレビュー)
  • mapcn.dev/r/map.json — shadcn add 用レジストリ JSON
  • MapLibre GL JS — レンダリングエンジン(MIT ライセンス)
  • shadcn/ui — コンポーネント配布モデルの仕様元
  • CARTO Basemaps — デフォルトタイルの利用条件