시리즈 2: My Next.js 16 + OpenLayers + TypeScript 스타터 키트 — 현대적인 지도 앱 설정
Source: Dev.to
왜 다시 만들었나요
몇 주 전, 매핑 스타터‑킷 시리즈의 첫 단계로 Next.js + Leaflet 스타터 킷을 공개했습니다. 그 작업을 마무리하고 나니 다음 논리적인 단계는 OpenLayers였습니다—특히 더 고급 GIS 워크플로우를 다루는 개발자들을 위해서죠.
Leaflet은 가볍고 인터랙티브한 지도에 훌륭하지만, 많은 GIS‑기반 프로젝트는 벡터 타일, 커스텀 투영법, 고정밀 기하학 렌더링, 고급 인터랙션, 대용량 GeoJSON 데이터셋의 성능 좋은 처리를 필요로 합니다. 바로 이런 부분에서 OpenLayers가 빛을 발합니다.
전체 스타터를 처음부터 다시 구축했습니다—UI, 구조, 개발자 경험은 동일하지만 OpenLayers 기반으로 실제 GIS 사용 사례를 지원하도록 했습니다. 이는 Leaflet 버전을 대체하는 것이 아닙니다. OpenLayers 킷이 공개되면, 현대적인 벡터‑타일 기반 지도와 3D 시각화를 위한 Next.js + MapLibre 스타터 킷도 완성할 예정입니다.
Vanilla OpenLayers와 Leaflet 스타터와 동일한 Google‑Maps‑스타일 UI를 갖춘, 프로덕션‑레디 Next.js 16 스타터입니다.
기술 스택
| 기술 | 버전 | 목적 |
|---|---|---|
| Next.js | 16 | App Router, Server Components |
| React | 19 | UI 프레임워크 |
| OpenLayers | 10 | 매핑 (vanilla, 래퍼 없음) |
| TypeScript | 5 | 타입 안전성 |
| Tailwind CSS | 4 | 스타일링 |
| shadcn/ui | 최신 | 접근성 UI 컴포넌트 |
기능
- 기본 지도 설정 – OpenLayers
Map및View를 올바르게 초기화 - 다중 타일 제공자 – OpenStreetMap, 위성, 다크 모드
- 테마 인식 타일 – 라이트/다크 모드에 따라 자동 전환
- GeoJSON 렌더링 – 커스텀 스타일링 및 범위 맞춤
- 국가 검색 – 디바운스된 검색과 키보드 내비게이션
- 커스텀 마커 – 팝업과 함께 어디든 마커 추가
- 컨텍스트 메뉴 – 오른쪽 클릭으로 좌표 복사, 마커 추가, 측정
- 측정 도구 – 인터랙티브 드로잉을 통한 거리·면적 측정
- POI 관리 – 14개 카테고리 전 CRUD,
localStorage영속성 - 위치 찾기 – 정확도 원과 함께 사용자 위치 탐색
- 반응형 레이아웃 – 모바일 드로어, 데스크톱 사이드바
- 에러 경계 – 우아한 오류 처리
- 다크 모드 – 전체 테마 지원
좌표 변환 유틸리티
OpenLayers는 Web Mercator 투영법을 사용해 [lng, lat] 형식을 쓰는 반면, 대부분의 API는 [lat, lng] 형식을 사용합니다. 스타터에는 변환을 도와주는 헬퍼가 포함되어 있습니다:
import { latLngToOL, olToLatLng } from "@/lib/utils/coordinates";
// [lat, lng] → OpenLayers 좌표 변환
const olCoord = latLngToOL([51.505, -0.09]);
// 다시 변환
const latLng = olToLatLng(olCoord);
올바른 레이어 관리
OpenLayers는 레이어의 추가·제거를 명시적으로 해야 합니다. 이를 간소화하는 훅이 제공됩니다:
const { tileProvider } = useMapTileProvider();
;
메모리 누수 방지
OpenLayers는 수동 정리가 필요합니다. 모든 컴포넌트가 리소스를 올바르게 해제합니다:
useEffect(() => {
// ... 지도 초기화
return () => {
map.setTarget(undefined); // DOM에서 분리
map.dispose(); // 리소스 해제
};
}, []);
예시 컴포넌트
지도 페이지
"use client";
import { MapProvider } from "@/contexts/MapContext";
import { OpenLayersMap, OpenLayersTileLayer } from "@/components/map";
export default function MapPage() {
return (
);
}
GeoJSON 레이어
import { OpenLayersGeoJSON } from "@/components/map";
;
지도 컨트롤
import { useMapControls } from "@/hooks/useMapControls";
function MapControls() {
const { zoomIn, zoomOut, resetView, flyTo } = useMapControls();
return (
Zoom In
Zoom Out
Reset
flyTo([51.505, -0.09], 13)}>Fly to London
);
}
POI 패널
import { usePOIManager } from "@/hooks/usePOIManager";
function POIPanel() {
const { pois, addPOI, deletePOI, exportGeoJSON } = usePOIManager();
const handleAdd = () => {
addPOI("Coffee Shop", 51.505, -0.09, "food‑drink", "Great espresso");
};
return (
Add POI
Export
{pois.map((poi) => (
{poi.title}
deletePOI(poi.id)}>Delete
))}
);
}
측정 패널
import { useMeasurement } from "@/hooks/useMeasurement";
function MeasurementPanel() {
const { startMeasurement, clearMeasurement, distance, area } = useMeasurement();
return (
startMeasurement("distance")}>Measure Distance
startMeasurement("area")}>Measure Area
Clear
{distance > 0 &&
Distance: {(distance / 1000).toFixed(2)} km
}
{area > 0 &&
Area: {(area / 1000000).toFixed(2)} km²
}
);
}
설치
# 저장소 복제
git clone https://github.com/wellywahyudi/nextjs-openlayers-starter.git
cd nextjs-openlayers-starter
# 의존성 설치
npm install
# 개발 서버 시작
npm run dev
http://localhost:3000/map에 접속하면 바로 사용할 수 있습니다.
설정
기본 지도 설정 (constants/map-config.ts)
export const DEFAULT_MAP_CONFIG: MapConfig = {
defaultCenter: [51.505, -0.09], // [lat, lng]
defaultZoom: 13,
minZoom: 3,
maxZoom: 18,
};
타일 제공자 (constants/tile-providers.ts)
export const TILE_PROVIDERS: TileProvider[] = [
{
id: "custom",
name: "My Custom Tiles",
url: "https://your-tile-server/{z}/{x}/{y}.png",
attribution: "© Your Attribution",
maxZoom: 19,
category: "standard",
},
// ...existing providers
];
이 스타터가 적합한 사람
- 🗺️ GIS 애플리케이션 구축 (공간 분석, 커스텀 투영법, 벡터 타일)
- 📊 대용량 데이터셋 렌더링 (10 000+ 피처, 복잡한 기하학)
- 🎯 고급 인터랙션 필요 (그리기, 편집, 스냅, 측정)
- 🚀 지도 중심 앱 프로토타이핑 (대시보드, 분석, 시각화)
- 📚 OpenLayers를 배우고 깔끔하고 현대적인 시작점을 찾는 경우
단순히 마커가 있는 지도만 필요하다면 Leaflet 버전이 더 간단합니다. 강력함과 유연성을 원한다면 이 OpenLayers 스타터가 적합합니다.
OpenLayers 특성
- 투영법 처리 – OpenLayers는 기본적으로 EPSG:3857 (Web Mercator)를 사용하며
[lng, lat]를 기대합니다. 스타터는 외부[lat, lng]입력을 자동으로 변환합니다:
// 외부 API는 [lat, lng] 사용
const userInput = [51.505, -0.09];
// OpenLayers 형식으로 변환
const olCoord = latLngToOL(userInput);
// OpenLayers에 적용
map.getView().setCenter(olCoord);
-
레이어 순서 (하단 → 상단):
- 기본 타일 레이어
- GeoJSON 벡터 레이어
- POI 벡터 레이어
- 마커 벡터 레이어
- 측정 벡터 레이어
-
트리‑쉐이킹 가능한 임포트:
// ✅ 좋은 예 – 트리‑쉐이킹 가능
import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
// ❌ 나쁜 예 – 전체를 임포트
import * as ol from "ol";
- 번들 크기: Leaflet ~50 KB, OpenLayers ~90 KB. 추가 GIS 기능을 고려하면 충분히 정당한 증가입니다.
로드맵
| 스타터 | 상태 |
|---|---|
| Next.js + Leaflet | ✅ 사용 가능 |
| Next.js + OpenLayers | ✅ 사용 가능 |
| Next.js + MapLibre GL | 🚧 예정 |
동일한 UI, 동일한 개발자 경험, 다른 매핑 라이브러리—프로젝트에 맞는 것을 선택하세요.
기여
이 프로젝트는 오픈 소스(MIT 라이선스)입니다. 버그를 발견하거나 아이디어가 있거나 기여하고 싶다면:
- 🐛 이슈 열기
- 💡 토론 시작
- 🔧 PR 제출
코드베이스는 깔끔하고, 문서화되어 있으며, 초보자에게도 친숙합니다.