Supun Vindula
The journal
GeospatialVue.js

Advanced Leaflet Integration in Vue 3 Without vue-leaflet

Most Vue+Leaflet tutorials use the vue-leaflet wrapper, but in this GIS project, I built a more modular and flexible integration using vanilla Leaflet directly in Vue 3 components.

sv.
Supun Imbulana21 May 2025 · 3 min read

Most Vue+Leaflet tutorials use the vue-leaflet wrapper, but in this GIS project, I built a more modular and flexible integration using vanilla Leaflet directly in Vue 3 components. This allowed me to support complex use cases like:

  • Multiple map tile layers with seamless toggling

  • Dozens of dynamic markers rendered from backend data

  • Rich popup UIs using Vue components rendered to HTML

  • Support for multiple L.Map instances

  • Full role-based dynamic layer rendering

This guide explains how I did it.

1. Rendering Markers Dynamically with Vue

Instead of vue-leaflet, I created a custom MapMarker.vue component that:

  • Injects a shared Leaflet Map instance

  • Creates a L.marker instance dynamically

  • Updates position reactively

  • Adds click event listeners

<script setup lang="ts">
import { inject, ref, watchEffect, onUnmounted } from 'vue';
import L from 'leaflet';

const props = defineProps<{ lat: number; lng: number; iconUrl?: string }>();
const emit = defineEmits(['click']);

const map = inject('MapContext'); // Provided by parent
const marker = ref<L.Marker | null>(null);

watchEffect(() => {
  if (!map || marker.value) return;

  const icon = props.iconUrl ? L.icon({ iconUrl: props.iconUrl, iconSize: [32, 32] }) : undefined;
  marker.value = L.marker([props.lat, props.lng], { icon }).addTo(map);
  marker.value.on('click', (e) => emit('click', e));
});

onUnmounted(() => {
  marker.value?.remove();
});
</script>

Switching Between Two Base Maps

I have added support for switching between a roadmap and satellite map using bringToFront() on tile layers.

const currentMap = ref<'road' | 'satellite'>('road');
const toggleBaseMap = () => {
  if (currentMap.value === 'road') {
    satelliteLayer.bringToFront();
    currentMap.value = 'satellite';
  } else {
    roadLayer.bringToFront();
    currentMap.value = 'road';
  }
};

Rendering Vue Popups on Leaflet Map

A common problem with Leaflet is integrating rich Vue content inside popups. I solved this by using a utility function to render a Vue component into static HTML, then injecting it into the Leaflet popup.

// render-utils.ts
import { renderToString } from 'vue/server-renderer';
import { h } from 'vue';

export async function renderComponentToHtml(component: any, props: Record<string, any>) {
  const vnode = h(component, props);
  const html = await renderToString(vnode);
  const el = document.createElement('div');
  el.innerHTML = html;
  return { el };
}
const showPopup = async (map, latlng, data) => {
  const popupContent = await renderComponentToHtml(MyPopupComponent, { data });
  L.popup().setLatLng(latlng).setContent(popupContent.el).openOn(map);
};

Supporting Multiple Leaflet Maps

I have scoped each map instance to its own container and provided the context using Vue’s dependency injection:

<script setup lang="ts">
import { provide, onMounted, ref } from 'vue';
import L from 'leaflet';

const map = ref<L.Map | null>(null);
onMounted(() => {
  map.value = L.map('map-id').setView([-42, 146], 7);
  provide('MapContext', map.value);
});
</script>

<template>
  <div id="map-id" style="height: 500px;"></div>
  <slot />
</template>

Why Avoid vue-leaflet?

  • Customization over abstraction

  • Better control over Leaflet panes and z-indexing

  • Easier integration with Vue Teleport and composition API

  • No dependency on wrapper bugs or limitations

With Vue 3's Composition API and Leaflet’s flexibility, you don’t need heavy wrappers like vue-leaflet. This custom approach enabled me to:

  • Support multi-map instances

  • Render dynamic markers from backend

  • Show Vue-powered popups with interactive buttons

  • Toggle map layers conditionally based on user role

This method is especially powerful for enterprise GIS systems where performance and fine-grained control matter.

Originally published on Substack. Original publication date retained.