npm install react react-dom leaflet
npm install react-leaflet
npm install -D @types/leaflet
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
export default function Map() {
return (
<MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[51.505, -0.09]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
);
}
홈페이지에서 알려준 방법대로 설치와 import를 하면 Window is not defined
라는 에러가 뜬다
window는 client-side에만 존재하고 Next.js 에서는 SSR을 지원하기 때문에 처음 웹 페이지를 렌더링 할 때 window나 document 전역객체를 사용할 수 없다.
해결하기 위해서 Next.js에서 dynamic 이라는 함수를 제공한다.
위의 홈페이지에 들어가면 Next.js에서 Leaflet을 사용해 띄워놓은 페이지와 코드를 볼 수 있다.
사용 방법
1. Map 컴포넌트를 작성하고
2. index 에서 dynamic import를 한 뒤
3. 필요한 곳에서 index.js 를 import 해 사용한다.
지도의 크기를 지정해주고 css 까지 import 해주면 지도를 불러올 수 있다.
import "leaflet/dist/leaflet.css"; ✅ 추가
...
return(
<MapContainer
center={[51.505, -0.09]}
zoom={13}
scrollWheelZoom={true}
style={{ width: "100%", height: "100vh" }} ✅ 추가
>
</MapContainer>
)
이 방법으로 지도를 띄우고 마커를 표시하는 데는 성공했지만
주소 검색 시 위치 변경을 해주는 곳에서 props가 변경 되었을 때 컴포넌트가 재 렌더링 되지 못해 기능 구현이 되지 않았다.
위의 방법 처럼 원래 index를 import 해오던 것을 참조해서 가져오지 않고 컴포넌트 내에서 바로 import를 해서 해결했다.
import dynamic from "next/dynamic";
export default function IndexPage() {
const MapWithNoSSR = dynamic(() => import("./Map"), {
ssr: false
});
return (
<>
<MapWithNoSSR />
</>
);
}
Map을 필요한 곳에서 기능을 추가해서 사용해야 하기 때문에 Map안에 children prop을 사용한다.
children을 사용해서 Marker 기능을 추가해보자.
ProjectTable.tsx
import dynamic from "next/dynamic";
import { useRecoilValue } from "recoil";
import { location } from "../shared/state/atoms";
const ProjectTable = () => {
const getLocation = useRecoilValue(location);
// ✅ dynamic import
const MapWithNoSSR = dynamic(() => import("../shared/components/Map"), {
ssr: false,
});
const MapMarkerNoSSR = dynamic(
() => import("../shared/components/MapMarker"), {
ssr: false,
});
return (
<>
<MapWithNoSSR getLocation={getLocation}>
<MapMarkerNoSSR />
</MapWithNoSSR>
<Search />
</>
);
};
Map.tsx
import React from "react";
import "leaflet/dist/leaflet.css";
import { MapContainer, TileLayer } from "react-leaflet";
interface MapProps {
children?: React.ReactNode;
getLocation: { x: number; y: number };
}
const Map = ({ getLocation, children }: MapProps) => {
return (
<MapContainer
center={[getLocation.x, getLocation.y]}
zoom={10}
scrollWheelZoom={true}
style={{ width: "100%", height: "550px" }}
>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{children} // ✅ check
</MapContainer>
);
};
export default Map;
MapMarker.tsx
import { useRecoilValue } from "recoil";
import { Marker } from "react-leaflet";
import L from "leaflet";
const MapMarker = () => {
const getProjectList = useRecoilValue(projectList);
const [mapMarker, setMapMarker] = useState<ReactElement | null>(null);
// ✅ Marker를 state에 넣어서 return
const getDynamicMarker = () => {
const blueIcon = L.icon({
iconUrl: "/blueIcon.svg",
iconSize: [30, 90],
});
setMapMarker(
<div>
{getProjectList?.data?.map(
(item: { lat: number; lon: number }, id: number) => (
<Marker
key={id}
position={[item.lat, item.lon]}
icon={blueIcon}
></Marker>
)
)}
</div>
);
};
useEffect(() => {
getDynamicMarker();
}, [getProjectList]);
return mapMarker;
};
export default MapMarker;
참고
React leaflet not rendering properly - 지도가 타일처럼 깨져서 나올 때
codeSandBox example - 지도 완성 샘플