Vitepress and leaflet.markercluster
VitePress pre-renders the app in Node.js during the production build, using Vue's Server-Side Rendering (SSR) capabilities: this fact prevents using 'leaflet.markercluster' (or its wrapper module 'vue-leaflet-markercluster') within a vitepress project. Let's say we create this simple Leaflet map within a Vue component imported within a vitepress project:
<template>
<div id="map">
<l-map
:zoom="9"
:center="[45.1, 9.1]"
:use-global-leaflet="true"
>
<l-tile-layer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" :attribution="attribution"/>
<l-marker-cluster-group>
<l-marker :lat-lng="[45.1, 9.1]"/>
<l-marker :lat-lng="[45.2, 9.2]"/>
<l-marker :lat-lng="[45.3, 9.3]"/>
</l-marker-cluster-group>
</l-map>
</div>
</template>
<script setup lang="ts">
import {LMarker, LMap, LTileLayer} from "@vue-leaflet/vue-leaflet";
import L, {marker} from 'leaflet'
globalThis.L = L
import {LMarkerClusterGroup} from 'vue-leaflet-markercluster'
import {ref} from 'vue'
import 'leaflet/dist/leaflet.css'
import 'vue-leaflet-markercluster/dist/style.css'
const attribution = ref("openstreetmap contributors")
const prefix = ref("leaflet")
</script>
<style scoped>
#map {
height: 50vh;
width: 100%;
}
</style>
While running this code with yarn docs:dev
or pnpm docs:dev
(aliases of vitepress dev docs
, see the vitepress documentation) it's fine, of course the leaflet.markercluster
SSR requirements break the build step. Luckily it's possible to build the project using the import()
syntax, commonly called dynamic import. There are different possible implementations (e.g. async components) but in this case I prefer using the classic JS syntax within the onBeforeMount()
hook, then we can fix the broken component this way:
<template>
<div id="map-photos"/>
</template>
<script setup lang="ts">
import "leaflet/dist/leaflet.css";
import "leaflet.markercluster/dist/MarkerCluster.css"
import "leaflet.markercluster/dist/MarkerCluster.Default.css"
import {onBeforeMount} from "vue";
onBeforeMount(async () => {
const L = await import("leaflet")
const {MarkerClusterGroup} = await import('leaflet.markercluster/dist/leaflet.markercluster')
const map = L.map("map-photos") // div id
map.setView([45.1, 9.1], 8)
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png").addTo(map);
let mcluster = new MarkerClusterGroup()
let marker1 = new L.Marker([45.1, 9.1])
let marker2 = new L.Marker([45.2, 9.2])
let marker3 = new L.Marker([45.3, 9.3])
marker1.addTo(mcluster)
marker2.addTo(mcluster)
marker3.addTo(mcluster)
mcluster.addTo(map)
})
</script>
<style scoped>
#map-photos {
width: 100%;
height: 60vh;
}
</style>
Note that onBeforeMount()
must be async to make it work. Last but not least the package.json
of this vitepress project contains these dependencies:
{
"name": "vitepress-ssr-build",
"version": "1.0.0",
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "pnpm i --frozen-lockfile && vitepress build docs",
"docs:serve": "vitepress serve docs",
"docs:preview": "vitepress preview docs --port 3000"
},
"license": "MIT",
"type": "module",
"dependencies": {
"@vue-leaflet/vue-leaflet": "^0.10.1",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"vitepress": "^1.0.0-rc.10",
"vue": "^3.3.4"
},
"devDependencies": {
"@tsconfig/recommended": "^1.0.2",
"@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.4",
"@types/leaflet.markercluster": "^1.5.2"
}
}