Reactアプリに地図を組み込みたいけれど、「maplibreとreactの使い方が分からない」「どのライブラリを選べばよいのか迷っている」と悩んでいませんか?
MapLibre GL JSは、Webブラウザ上にインタラクティブな地図を表示できるライブラリです。Reactと組み合わせる場合、maplibre-glを直接操作する方法と、react-map-gl/maplibreなどのReact向けライブラリを使う方法があります。ただし、地図を表示できても、再レンダリング時の処理やイベント管理、Next.jsのSSRに起因するエラーなどでつまずくこともあります。
そこで本記事では、ライブラリの選び方から、Vite・TypeScript環境での導入、Reactとの連携、Next.jsで発生しやすいエラーの解決方法まで、コード例を交えながら解説します。
ReactでMapLibreを使いたい方は、実際のコードを確認しながら、自分のプロジェクトに合った実装方法を見つけていきましょう。
Audibleプレミアムプラン30日間無料体験ReactでMapLibreを使う方法|直接利用とReact用ライブラリの違い

ReactでMapLibre GL JSを使う方法は、大きく分けて2つあります。maplibre-glを直接操作する方法と、React向けのラッパーライブラリを使う方法です。どちらも動きますが、地図インスタンスの生成や破棄、Stateとの連携の書き方が大きく変わります。この章では、両者の違いと選び方の基準を整理し、以降の章で実装する前の判断材料を揃えます。
MapLibreの概要や基本的な使い方は、MapLibre GL JSの基本をまとめた記事で詳しく解説しています。この記事では、その内容を前提に、Reactコンポーネントとして地図を組み込む方法に絞って説明します。

ReactでMapLibre GL JSを使うとできること
MapLibre GL JSは、ベクタータイルをWebGLで描画する地図ライブラリです。Reactと組み合わせると、地図を「ひとつのコンポーネント」として扱えるようになります。
たとえば、次のような画面を作れます。
- 検索フォームの入力値に応じて、地図の中心座標を切り替える
- 地図上でクリックした地点の座標を、Reactの状態として表示する
- サイドバーの一覧を選択すると、対応するマーカーの位置へ地図が移動する
- 地図の表示範囲に含まれるデータだけを、APIから取得し直す
地図の状態とReactの状態を連携させられることが、Reactで使う最大の利点です。一方で、地図は「自分で状態を持つ」ライブラリなので、そのままReactに組み込むと、二つの状態がずれる原因になります。この点が、次の節の比較につながります。
maplibre-glを直接使う方法とReact用ライブラリの違い
maplibre-glを直接使う方法は、Reactを介さず、MapLibreのAPIをuseEffectの中から呼び出す書き方です。地図の生成、イベント登録、後片付けをすべて自分で管理します。
import { useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
export default function MapDirect() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const map = new maplibregl.Map({
container: containerRef.current,
style: '<https://demotiles.maplibre.org/style.json>',
center: [139.7, 35.68],
zoom: 5,
});
return () => {
map.remove();
};
}, []);
return <div ref={containerRef} style={{ width: '100%', height: '400px' }} />;
}
この方法の利点は、MapLibreのAPIがそのまま使えることです。公式ドキュメントの例をほぼそのまま移植でき、Reactに依存しない部分の知識も流用できます。
一方で、次の作業をすべて自分で行う必要があります。
- 地図インスタンスを一度だけ生成し、再レンダリングのたびに作り直さない
- イベントリスナーを登録し、アンマウント時に解除する
- Reactの状態を地図に反映する処理(中心座標の更新など)を書く
map.remove()で地図を破棄し、メモリリークを防ぐ
React用ライブラリは、これらのライフサイクル管理をコンポーネントの内部に隠します。react-map-gl/maplibreの公式ドキュメントでも、Mapコンポーネントがアンマウントされると内部リソースを解放する仕組みになっていると説明されています。
両者の違いを整理すると、次のとおりです。
| 観点 | maplibre-gl直接利用 | React用ラッパー |
|---|---|---|
| 地図の生成・破棄 | useEffectで自分が書く | コンポーネントが担当 |
| 公式APIとの対応 | そのまま使える | ラッパー越しに使う |
| Reactの状態との連携 | 自分で同期処理を書く | Propsで宣言的に書ける |
| 学習コスト | ライフサイクルの理解が必要 | Props中心で始めやすい |
| 向いている場面 | 細かい制御、ライブラリ非依存の実装 | 一般的なReactアプリ、Stateとの連携が多い画面 |
結論として、地図の状態をReactの状態と密に連携させたい場合は、React用ラッパーが向いています。逆に、地図の細かい制御が多く、ラッパーのAPIでは表現しにくい場合は、直接利用のほうが素直に書けることもあります。
ラッパーの中には、地図の状態をReact側で再管理するものもあります。その場合、地図側でsetMaxZoomなどを直接呼ぶと、Propsと実際の地図の設定がずれることがあります。ラッパーを使うときは、同じ設定をMapLibreのAPIで直接変更しないことを基本にしてください。
@vis.gl/react-maplibreとreact-map-gl/maplibreの選び方
MapLibre向けのReactラッパーとして、現在よく使われているのは次の2つです。
react-map-gl/maplibre:
react-map-glというスイートの一部です。react-map-glは、もともとMapbox GL JS向けのReactラッパーとして作られました。現在はMapLibre向けのエントリポイントとしてreact-map-gl/maplibreが用意されており、利用にはmaplibre-glのバージョン4以上が必要とされています。Mapboxを併用するプロジェクトでも、同じ開発者体験で扱えるのが特徴です。
@vis.gl/react-maplibre:
react-map-glから独立したMapLibre専用のパッケージです。公式ドキュメントでは、MapLibre GL JSのAPIやタイプが、Mapbox GL JSから分岐して進化してきたことが独立の理由として挙げられています。PropsとメソッドをMapLibreの対応APIに1対1で対応させ、Reactの状態と地図の状態を一貫して扱う「完全にリアクティブなラッパー」を目指しているとされています。
どちらを選ぶかは、次の基準で判断すると迷いにくくなります。
- Mapboxとの併用が必要、または既存の
react-map-glを使っている:react-map-gl/maplibreを選びます。既存の知識やコードをそのまま活かせます。 - MapLibre専用で新規に作る:
@vis.gl/react-maplibreの公式ドキュメントを確認し、APIの方針が自分の設計に合うか見てから選びます。 - どちらも迷う場合:それぞれのPropsの一覧を比較し、使いたい機能が両方に揃っているかを確認してください。
ここで注意したいのは、ライブラリのメンテナンス状況は時期によって変わる点です。本記事の執筆時点では、どちらも公式ドキュメントが公開されていますが、導入前にはnpmの最新バージョンとGitHubのリリース情報を必ず確認してください。特定のライブラリを無条件におすすめすることは避け、公式情報を基準に選んでください。
この記事では、以降の実装でreact-map-gl/maplibreを例に使います。 理由は、公式ドキュメントに導入例がまとまっており、既存のReactプロジェクトに組み込みやすいためです。@vis.gl/react-maplibreを使う場合も、考え方(Props、コンテナの高さ、スタイルURLの指定)は共通しています。
Reactアプリに地図を表示する手順

この章では、Vite・TypeScriptで作ったReactアプリに、MapLibreの地図を表示するまでの最小構成を作ります。前章で説明したとおり、以降の例ではreact-map-gl/maplibreを使います。手順は、パッケージのインストール、CSSと地図コンテナの設定、初期座標とスタイルの指定の順に進めます。ここで表示までできれば、次の章でReactの状態と連携させる準備が整います。
Vite・TypeScript環境に必要なnpmパッケージをインストールする
新規プロジェクトを作る場合は、Viteのテンプレートから始めると設定が少なく済みます。Node.jsはLTS版を使い、Viteの要件に合ったバージョンを用意してください。
npm create vite@latest my-map-app -- --template react-ts
cd my-map-app
npm install
次に、地図に必要なパッケージを追加します。
npm install react-map-gl maplibre-gl
それぞれの役割は次のとおりです。maplibre-glは地図を描画するエンジン本体で、react-map-glはそのReact用のコンポーネントです。react-map-gl/maplibreは、maplibre-glを内部で利用する入口にあたります。両方をインストールする必要がある点に注意してください。react-map-glだけを入れると、maplibre-glが見つからないというエラーになります。
既存のプロジェクトに追加する場合も、このコマンドだけで済みます。TypeScriptの型定義は両パッケージに含まれているため、別途@typesを入れる必要はありません。
MapLibreのCSSと地図コンテナを設定する
地図の描画には、MapLibre本体が用意しているCSSの読み込みが必要です。このCSSがないと、ズームのボタンや帰属表示(クレジット)が正しく表示されず、地図の見た目も崩れやすくなります。
地図を表示するコンポーネントの先頭で、次のようにCSSを読み込みます。
import 'maplibre-gl/dist/maplibre-gl.css';
次に、地図を置く場所を決めます。ここで最も多いつまずきは、地図の親要素の高さが0になることです。地図は親要素のサイズいっぱいに広がるため、高さが決まっていないと、表示されているのに地図が見えない状態になります。
index.cssの見た目を整えるために、html、body、#rootの高さを画面の高さに揃えておくと安心です。
html,
body,
#root {
height: 100%;
margin: 0;
}
地図を置く要素には、幅と高さを明示的に指定します。画面全体に地図を表示したい場合は、次のように指定します。
<div style={{ width: '100%', height: '100vh' }}>
{/* ここに地図を配置する */}
</div>
一部の領域だけに地図を置く場合は、height: '400px'のように固定値を指定できます。どちらの場合も、地図の親要素の高さが確定していることが、表示を成功させる条件です。
初期座標・ズーム・地図スタイルを指定して表示する
ここまでの準備ができたら、地図を表示するコンポーネントを作ります。src/MapView.tsxを次のように作成してください。
import Map from 'react-map-gl/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
export default function MapView() {
return (
<div style={{ width: '100%', height: '100vh' }}>
<Map
initialViewState={{
longitude: 139.7671,
latitude: 35.6812,
zoom: 10,
}}
mapStyle="<https://demotiles.maplibre.org/style.json>"
style={{ width: '100%', height: '100%' }}
/>
</div>
);
}
App.tsxからこのコンポーネントを読み込めば、東京周辺の地図が表示されます。
それぞれのPropsの意味を説明します。initialViewStateは、地図を最初に表示する中心座標(longitudeが経度、latitudeが緯度)とズームレベルを指定します。ここで指定するのは「初期」の値であり、以降は地図の操作(ドラッグやズーム)に応じて地図が動きます。Reactの状態で地図の位置を常に制御したい場合は、後の章で説明する方法を使います。
mapStyleは、地図の見た目を決めるスタイルのURLです。ここでは、MapLibreが公式に用意しているデモ用のスタイルを使っています。デモ用のスタイルは動作確認には便利ですが、商用サイトに組み込む用途には向きません。実際のサービスでは、地図スタイルを提供する業者を選び、利用規約とAPIキーの要否を確認してください。
APIキーが必要な場合は、mapStyleにURLとキーを組み合わせて指定します。たとえばMapTilerのようなサービスでは、次のように書きます。
mapStyle={`https://api.maptiler.com/maps/streets/style.json?key=${import.meta.env.VITE_MAPTILER_KEY}`}
この場合、キーは.envファイルにVITE_MAPTILER_KEY=...の形式で保存します。ただし、Viteの環境変数は最終的にブラウザに配信されるJavaScriptに含まれるため、秘密情報として隠すことはできません。キーを使うサービス側で利用ドメインを制限するなど、公開されても問題が起きにくい設定にしてください。
地図が表示されないときの確認ポイント
地図が真っ白なままのときは、次の順に確認すると原因を絞り込みやすくなります。最初に、地図を囲むすべての親要素に高さが指定されているかを見てください。次に、maplibre-gl/dist/maplibre-gl.cssが読み込まれているかを確認します。そして、mapStyleのURLがブラウザで直接開けるか、ネットワークタブでエラーになっていないかを見ます。APIキーが必要なスタイルで、キーが未設定または誤っていると、地図は表示されずにエラーだけが出ることがあります。
ここまでで、地図を表示する最小構成ができました。次の章では、このコンポーネントの中で地図インスタンスをどう管理するか、ReactのuseRefやuseEffectをどう使い分けるかを説明します。
Reactコンポーネントと地図を連携する

前の章で地図を表示できるようになりました。この章では、その地図をReactの状態や画面の操作と結びつけます。地図のクリックで座標を表示する、ボタンで地図を移動させる、地図の移動に合わせて数値を更新する、といった連携が中心です。
ここで理解しておきたいのは、地図の内部状態(中心座標、ズーム、回転など)と、Reactの状態(useStateで持つ値)は、もともと別々に管理されているという点です。両者を正しくつなぐには、どの値をどこで管理し、どのタイミングで同期させるかを決める必要があります。
useRefとuseEffectで地図インスタンスを管理する
react-map-gl/maplibreのMapコンポーネントを使う場合、地図インスタンスの生成と破棄はコンポーネントが自動で行います。そのため、useEffectでnew maplibregl.Map()を書く必要はありません。ただし、地図を外側から操作したいときや、MapLibreのイベントに直接接続したいときには、地図インスタンスへの参照が必要になります。
そのときに役立つのがuseRefです。useRefは、再レンダリングをまたいで値を保持し、再レンダリングのきっかけにはならない箱です。Mapコンポーネントにrefを渡すと、その箱に地図の操作用オブジェクトが入ります。型はMapRefを使います。
import { useRef } from 'react';
import Map, { type MapRef } from 'react-map-gl/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
export default function MapWithRef() {
const mapRef = useRef<MapRef>(null);
return (
<div style={{ width: '100%', height: '100vh' }}>
<button
onClick={() => mapRef.current?.flyTo({ center: [135.5, 34.7], zoom: 12 })}
>
大阪へ移動
</button>
<Map
ref={mapRef}
initialViewState={{ longitude: 139.7671, latitude: 35.6812, zoom: 10 }}
mapStyle="<https://demotiles.maplibre.org/style.json>"
style={{ width: '100%', height: '100%' }}
/>
</div>
);
}
ボタンのクリック時にflyToを呼べるのは、useRefで地図の参照を保持しているからです。このとき地図の再生成は起きません。
一方、useEffectは「地図の準備が整ったあとに、副作用を登録する」ための仕組みです。副作用とは、イベントリスナーの登録、外部APIへの通信、タイマーの設定などを指します。useEffectの中で登録したものは、戻り値の関数(クリーンアップ)で必ず解除します。
それぞれの役割を整理すると、次のようになります。
| 仕組み | 主な役割 | 再レンダリングのきっかけになるか |
|---|---|---|
useState | 画面に表示する値(座標、選択中の項目など)を持つ | なる |
useRef | 地図インスタンスの参照など、再描画に関係しない値を持つ | ならない |
useEffect | 地図の準備後に行う登録処理と、その後片付け | ならない |
ここでのポイントは、地図インスタンスをuseStateに入れないことです。地図の参照は表示に直接使わないため、useRefで持つのが適しています。ただし、後述するイベントの登録では、地図インスタンスが準備できたタイミングを知る必要があります。その場合は、後で説明するonLoadで状態として受け取る方法が扱いやすくなります。
再レンダリング・イベント重複・メモリリークを防ぐ
Reactと地図を連携させるとき、最も多いトラブルは「同じ処理が何度も実行される」ことです。原因は、大きく3つに分けられます。
一つ目は、地図インスタンスを不要に作り直すことです。Mapコンポーネントのkeyを親の状態で変えると、コンポーネントは破棄されて再生成されます。地図の再生成は重く、表示がちらつく原因にもなります。keyは本当に地図を作り直したい場合だけに使い、通常の状態変化では変えないでください。
二つ目は、イベントリスナーの重複登録です。useEffectの依存配列に毎回変わる値(オブジェクトや関数)を入れると、再レンダリングのたびにリスナーが追加されてしまいます。解除処理(off)を書き忘れると、クリックのたびに処理が増えていきます。React 18以降の開発モード(Strict Mode)では、useEffectが意図的に2回実行されます。そのため、解除処理が正しく書かれていれば、開発中に二重登録の不具合が見つかるようになっています。
三つ目は、地図の破棄漏れです。react-map-gl/maplibreのMapコンポーネントは、アンマウント時に内部のリソースを解放します。そのため、Mapコンポーネントを使っている限り、map.remove()を自分で呼ぶ必要はありません。ただし、maplibre-glのMapを別途newで作っている場合は、その地図を自分で破棄する必要があります。同じ画面で二つの地図インスタンスを管理すると、メモリ消費と不具合の原因になるため、注意してください。
また、onMoveのように頻繁に発火するイベントでは、そのたびに重い処理を行わないことが大切です。地図の移動中は毎フレームのように呼ばれるため、setStateを多用すると再レンダリングが連続して発生します。移動中に必要なのが座標だけであれば、その値だけを保存し、計算や通信はonMoveEndなど移動が終わったタイミングに移すと、操作が滑らかになります。
地図のクリック・移動・ズームをReactの状態に反映する
地図の操作をReactの状態に反映させる代表的な方法は、Propsのイベントハンドラを使うことです。react-map-gl/maplibreでは、クリックや移動などの操作をPropsの関数で受け取れます。useEffectを書く必要はありません。
ここでは、地図の表示位置をReactの状態で管理する「制御されたビュー(controlled view)」の書き方を紹介します。viewStateをuseStateで持ち、onMoveで更新します。
import { useState } from 'react';
import Map from 'react-map-gl/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
export default function ControlledMap() {
const [viewState, setViewState] = useState({
longitude: 139.7671,
latitude: 35.6812,
zoom: 10,
});
const [clicked, setClicked] = useState<{ lng: number; lat: number } | null>(null);
return (
<div style={{ width: '100%', height: '100vh' }}>
<p>
ズーム: {viewState.zoom.toFixed(2)}
{clicked && ` / クリック地点: ${clicked.lat.toFixed(4)}, ${clicked.lng.toFixed(4)}`}
</p>
<Map
{...viewState}
onMove={(evt) => setViewState(evt.viewState)}
onClick={(evt) => setClicked({ lng: evt.lngLat.lng, lat: evt.lngLat.lat })}
mapStyle="<https://demotiles.maplibre.org/style.json>"
style={{ width: '100%', height: '100%' }}
/>
</div>
);
}
この例では、地図をドラッグしたりズームしたりするたびに、画面上部のズーム値が更新されます。地図の操作がReactの状態に伝わり、その結果を別の場所でも使えるようになります。逆に、ボタンなどでsetViewStateを変えると、地図の表示位置がその値に従って動きます。
ここでinitialViewStateではなく{...viewState}を使っている点に注目してください。initialViewStateは初期値だけを指定するため、Reactの状態で地図を制御するなら、longitude、latitude、zoomを直接渡す形にします。どちらの形で書くかは、地図をReactの状態で制御したいかどうかで決めます。
クリック時の座標は、evt.lngLatからlngとlatを取り出し、数値として保存しています。lngLatオブジェクトをそのままuseStateに入れても動きますが、必要な値だけをプレーンなオブジェクトに変換して保存すると、比較や表示が扱いやすくなります。
地図の内部イベントのうち、Propsに用意されていないものを扱いたいときは、onLoadで地図インスタンスを受け取り、useEffectで登録します。次の例は、ズームが終わったタイミングで値を取得する方法です。
import { useEffect, useState } from 'react';
import Map from 'react-map-gl/maplibre';
import type { Map as MapLibreMap } from 'maplibre-gl';
export default function ZoomLogger() {
const [map, setMap] = useState<MapLibreMap | null>(null);
useEffect(() => {
if (!map) return;
const handleZoomEnd = () => {
console.log('zoom:', map.getZoom());
};
map.on('zoomend', handleZoomEnd);
return () => {
map.off('zoomend', handleZoomEnd);
};
}, [map]);
return (
<Map
initialViewState={{ longitude: 139.7671, latitude: 35.6812, zoom: 10 }}
mapStyle="<https://demotiles.maplibre.org/style.json>"
style={{ width: '100%', height: '100vh' }}
onLoad={(evt) => setMap(evt.target)}
/>
);
}
この実装のポイントは、リスナーの登録を地図が準備できたあとに行うことです。onLoadで地図インスタンスをmapという状態に保存し、その変化をきっかけにuseEffectでリスナーを登録します。クリーンアップでmap.offを呼ぶため、アンマウント時やStrict Modeの再実行時にも、イベントが重複しません。
まとめると、地図の操作を画面に反映するときは、まずPropsのイベントを使い、PropsにないものだけをonLoadとuseEffectの組み合わせで扱うのが基本です。次の章では、この連携を前提に、Next.jsでMapLibreを使う際の注意点を説明します。
Next.jsでMapLibreを使う方法とよくあるエラーの解決策
Next.jsでMapLibreを使うと、Reactアプリとは別の種類のつまずきが増えます。最大の原因は、Next.jsがページをサーバー側でも先に描画する(SSR)ことです。サーバーにはwindowやdocumentが存在しないため、地図ライブラリがそれらに触れた時点でエラーになります。この章では、App Routerでの正しい書き分けと、表示されない・真っ白になる・本番だけ壊れるといった代表的な問題の切り分け方を説明します。
前章までのコードは、Vite環境を前提にしていました。Next.jsでは、以下のように「サーバーで動くコード」と「ブラウザで動くコード」を分けることが基本になります。
App Routerで"use client"や動的インポートを使って地図を表示する方法
App Routerでは、コンポーネントはデフォルトでサーバーコンポーネントになります。地図を表示するコンポーネントは、ブラウザでしか動かないため、クライアントコンポーネントにする必要があります。そこで、次のような3層構成にすると整理しやすくなります。
app/page.tsx:サーバーコンポーネントとして、ページの枠組みを作るcomponents/MapClient.tsx:"use client"を付け、next/dynamicで地図をssr: falseで読み込むcomponents/MapView.tsx:"use client"を付け、実際にreact-map-gl/maplibreを使う
app/page.tsxは次のようにします。
import MapClient from '@/components/MapClient';
export default function Page() {
return (
<main style={{ width: '100%', height: '100vh' }}>
<MapClient />
</main>
);
}
components/MapClient.tsxでは、地図を動的に読み込みます。
'use client';
import dynamic from 'next/dynamic';
const MapView = dynamic(() => import('./MapView'), {
ssr: false,
loading: () => (
<div style={{ width: '100%', height: '100%' }}>地図を読み込み中...</div>
),
});
export default function MapClient() {
return <MapView />;
}
components/MapView.tsxには、前章までの地図コードを移します。
'use client';
import Map from 'react-map-gl/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
export default function MapView() {
return (
<Map
initialViewState={{ longitude: 139.7671, latitude: 35.6812, zoom: 10 }}
mapStyle="<https://demotiles.maplibre.org/style.json>"
style={{ width: '100%', height: '100%' }}
/>
);
}
ここでssr: falseを、サーバーコンポーネントのpage.tsxに直接書かないよう注意してください。App Routerでは、next/dynamicのssr: falseはクライアントコンポーネントの中でのみ使えます。そのため、"use client"を付けた薄いラッパー(MapClient.tsx)を一つ挟むのが定石です。
もう一つ重要なのは、ssr: falseは、その動的インポートに対してだけ効くという点です。サーバーで実行されるファイルがmaplibre-glを通常のimportで読み込むと、そのモジュールの読み込み時にエラーが起きることがあります。地図関連のコードは、必ずMapView.tsxのようなクライアント専用のファイルに閉じ込め、サーバー側のファイルから直接インポートしないようにしてください。
なお、maplibre-gl/dist/maplibre-gl.cssのようなCSSは、MapView.tsxの中で読み込むのが安全です。CSSを読み込んでいるファイルがサーバーコンポーネントだと、思わぬ読み込み順の問題が起きることがあるためです。
window is not defined・真っ白な地図・CSSの表示崩れを解決する方法
ここでは、よく見られる症状ごとに原因と対策を整理します。
| 症状 | 主な原因 | 対策 |
|---|---|---|
ReferenceError: window is not defined | サーバー側でwindowにアクセスしている | 地図をssr: falseの動的インポートにする |
| 開発環境では動くが本番で真っ白 | 地図の親要素の高さが0、またはクライアント側でのみ読み込まれていない | 親要素に高さを指定し、next startで動作確認する |
| 地図は出るがボタンや帰属表示が崩れる | MapLibreのCSSが読み込まれていない | maplibre-gl/dist/maplibre-gl.cssをMapView.tsxで読み込む |
Hydration failedなどの警告 | サーバーとクライアントで描画結果が違う | 地図をssr: falseにし、loadingで同じ高さの枠を表示する |
window is not definedの多くは、前述のdynamicの書き方で解決します。注意したいのは、エラーのスタックトレースがnode_modulesの中を指していても、原因は「地図ライブラリをサーバーで読み込んでいる」ことにある点です。エラーの箇所だけを直すのではなく、地図を読み込んでいるファイルの依存関係をたどり、どこで静的にimportされているかを確認してください。
「真っ白な地図」は、地図のエラーではなくレイアウトの問題であることがほとんどです。次の3点を確認してください。まず、mainや地図の親要素にheightが指定されているかを見ます。次に、loadingの枠の高さが地図と同じかを確認します。枠が違うと、読み込み後に画面がずれて見えることがあります。最後に、ブラウザの開発者ツールで地図のコンテナのサイズを確認します。幅と高さがどちらも0なら、CSSの親子関係を見直してください。
MapLibreのWorker設定・バージョン互換性・本番ビルドの注意点
MapLibre GL JSは、タイルの読み込みや解析の一部をWeb Workerで行います。そのため、開発環境では動いていても、本番ビルドで読み込み先のファイルが見つからず、地図が表示されないことがあります。症状としては、コンソールにWorker関連のエラーや読み込み失敗が出るケースが多いです。
Workerの設定は、通常はライブラリに任せられます。ただし、Next.jsのバージョン更新や設定変更の直後に問題が出た場合は、まずブラウザのコンソールでエラーの内容を確認し、Workerのファイルが404になっていないかを見てください。どの設定で解決するかは、エラーの内容によって変わります。推測で設定を追加する前に、エラーメッセージとネットワークタブの結果を確認することをおすすめします。
バージョンの互換性は、地図ライブラリの組み合わせで問題になりやすい部分です。react-map-gl/maplibreは、maplibre-glのバージョン4以上を前提としています。そのため、次のコマンドで、プロジェクトにインストールされているmaplibre-glのバージョンを確認してください。
npm ls maplibre-gl
複数のバージョンが混在している場合(たとえば、依存関係の中に古いmaplibre-glが含まれている場合)、思わぬエラーが出ることがあります。その場合は、package.jsonで直接使うバージョンを指定し、package-lock.jsonを更新したうえで、node_modulesを削除して入れ直すと解消することがあります。
本番ビルドの確認では、npm run devだけで判断しないでください。次の手順で、本番と同じ条件で動作を確認します。
npm run build
npm run start
next startで地図が表示されてから、デプロイに進むと安心です。開発環境だけで動いているコードは、Strict Modeの二重実行やSSRの扱いの違いで、本番で問題が出ることがあります。
最後に、Next.jsでMapLibreを使うときの基本方針をまとめます。地図のコードは"use client"のファイルに閉じ込め、ssr: falseの動的インポートで読み込みます。サーバー側のファイルからは地図ライブラリを直接インポートしません。CSSは地図のファイルで読み込み、親要素の高さを明示します。バージョンはnpm lsで確認し、本番ではnpm run buildとnpm run startで動作を確かめます。この流れを守れば、多くのエラーは切り分けて解決できるはずです。
よくある質問(FAQ)
-
ReactでMapLibreを使うなら、どのライブラリがおすすめですか?
-
既存のReactプロジェクトで手早く始めたい場合は、
react-map-gl/maplibreが導入しやすい選択肢です。公式ドキュメントに導入例がまとまっており、maplibre-glのバージョン4以上を組み合わせて使います。MapLibre専用のラッパーとして@vis.gl/react-maplibreもあり、こちらはPropsと地図のAPIを対応させた設計が特徴です。どちらを選ぶかは、Mapboxとの併用が必要か、既存コードとの整合性を重視するかで判断してください。導入前には、npmの最新バージョンとGitHubのリリース情報を確認しておくと安心です。
-
maplibre-glを直接使う方法とreact-map-gl/maplibreの違いは何ですか? -
maplibre-glを直接使う方法は、地図の生成、イベント登録、破棄をすべてuseEffectの中で自分が書きます。MapLibreの公式APIをそのまま使えるため、ライブラリに依存しない知識を流用しやすいのが利点です。一方、react-map-gl/maplibreは地図のライフサイクルをコンポーネントが管理し、PropsとイベントハンドラでReactらしく書けます。Reactの状態と地図を連携させる場面では、後者のほうがコードを短く保てます。
-
ReactでMapLibreを使う場合、MapLibreのAPIキーは必要ですか?
-
答えは「地図スタイルや地図データの提供元によって異なる」です。地図を描画するライブラリ(
maplibre-glやreact-map-gl)は無料のオープンソースで、APIキーは不要です。一方、地図の見た目を決めるスタイルや、タイルを配信するサービスの多くは、利用にAPIキーや利用規約への同意を求めます。この記事のサンプルで使っているデモ用スタイルは動作確認向けです。商用サイトでは、利用するサービスの料金、利用上限、規約を確認してください。キーをフロントエンドのコードに含める場合は、利用ドメインの制限など、公開されても問題が起きにくい設定にしましょう。
-
Next.jsで
window is not definedが発生する原因は何ですか? -
Next.jsは、ページをサーバー側でも先に描画します。サーバーのNode.js環境には
windowやdocumentが存在しないため、地図ライブラリがそれらに触れた時点でエラーになります。解決策は、地図をnext/dynamicでssr: falseの指定付きで読み込み、ブラウザでだけ描画させることです。App Routerでは、ssr: falseを"use client"のファイルの中で使う必要があります。エラーが出ている箇所だけを直しても解決しないことがあるため、地図のコードを静的にimportしているファイルがないかも確認してください。
-
MapLibreの地図が表示されないときは、どこを確認すればよいですか?
-
まず、地図を囲むすべての親要素に高さが指定されているかを確認してください。高さが0になると、地図は描画されていても見えません。次に、
maplibre-gl/dist/maplibre-gl.cssが読み込まれているかを見ます。そして、ブラウザの開発者ツールで、コンソールのエラーとネットワークタブの失敗を確認します。mapStyleのURLが直接開けるか、APIキーが必要なスタイルでキーが正しく設定されているかも、よくある原因です。Next.jsでは、開発環境だけでなくnpm run buildとnpm run startで本番と同じ条件を確認すると、問題を早く見つけられます。
まとめ
ReactでMapLibreを使うには、maplibre-glを直接操作する方法と、React用ラッパーを使う方法の二つがあります。地図の状態をReactの状態と密に連携させたいなら、ラッパーを使うほうが見通しよく書けます。この記事では、公式ドキュメントに導入例があるreact-map-gl/maplibreを中心に扱いました。
地図を表示するには、react-map-glとmaplibre-glの両方をインストールし、maplibre-gl/dist/maplibre-gl.cssを読み込みます。地図の親要素には必ず高さを指定してください。高さが0になると、地図が描画されていても表示されません。初期座標とズームはinitialViewState、地図スタイルはmapStyleで指定します。
Reactと連携させるときは、地図の操作をPropsのイベントで受け取り、useStateで画面に反映するのが基本です。地図インスタンスへの参照が必要な場合はuseRefを使い、Propsにない内部イベントはonLoadで地図を受け取ったあとにuseEffectで登録します。その際は、必ずクリーンアップでイベントを解除し、リスナーの重複や不要な再生成を防いでください。
Next.jsでは、地図をサーバーで描画させないことが最も重要です。App Routerでは、"use client"を付けた薄いラッパーでnext/dynamicのssr: falseを使い、地図を読み込みます。window is not definedは、多くの場合この構成で解決します。本番で問題が出たときは、npm ls maplibre-glでバージョンを確認し、npm run buildとnpm run startで動作を確かめてください。
地図スタイルやタイルの提供サービスには、APIキーや利用規約が関わることがあります。導入前に、利用するサービスの条件を必ず確認してください。ライブラリの最新情報も、公式ドキュメントとリリース情報で確かめながら選ぶことが大切です。
WEBCOACH|副業・フリーランス特化型のオンラインWebデザインスクール
