Skip to content
On this page

VitePress pre-renders the app in Node.js during the production build, using Vue's Server-Side Rendering (SSR) capabilities: this fact poses some obstacle to use '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:

vue
<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>
<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 (alias of vitepress dev docs, see the vitepress documentation) it's fine, of course the SSR requirements broke the build step. Luckily it's possibile avoid the issue 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:

vue
<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>
<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.

Like my website? Pay me a coffee
References are available upon request. I hereby authorize the use of my personal data in compliance with the Italian D. Lgs. 196/2003, art. 13.