Vitepress and leaflet.markercluster
Tags: javascript, vue, typescript, maps
Published: 2024-05-21 18:22:48


## Vitepress and leaflet.markercluster

[VitePress pre-renders the app in Node.js during the production build, using Vue's Server-Side Rendering (SSR) capabilities](https://vitepress.dev/guide/ssr-compat): this fact prevents using 'leaflet.markercluster' (or its wrapper module ['vue-leaflet-markercluster'](https://github.com/veitbjarsch/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>
```

While running this code with `yarn docs:dev` or `pnpm docs:dev` (aliases of `vitepress dev docs`, see the [vitepress documentation](https://vitepress.dev/guide/getting-started#up-and-running)) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import). There are different possible implementations (e.g. [async components](https://vuejs.org/guide/components/async.html)) 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>
```

Note that `onBeforeMount()` must be async to make it work.
Last but not least the `package.json` of this vitepress project contains these dependencies:

```json
{
  "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"
  }
}
```