vue-neshan-maps

Typed Vue 3 maps with Neshan traffic and POI using Leaflet and MapLibre

Vue Neshan Maps

Bring your places to life.

Reactive maps for Vue 3 · MapLibre + Leaflet · TypeScript

npmCILicense

English · فارسی

Try the live demo · دموی فارسی

Get started · Feature guide · API reference · Examples · Nuxt 4

Developer documentation · Bilingual guides, complete API reference and interactive examples.

Package name: vue-neshan-maps. Existing users: migration and legacy compatibility. Future feature releases are published as vue-neshan-maps. The repository is now sajadjanat/vue-neshan-maps, with the demo at the matching GitHub Pages address.

Neshan vector map in a Vue application: Tehran, live traffic, places and custom geometry

Real Neshan basemap and live layers. Markers, the service area and the dashed connection are sample application data. The interface illustrates what you can build; the component supplies the map.

Build a location picker, show your branches, outline a delivery area or add live traffic to your app. Two Vue components connect Neshan's renderers to reactive state, with typed overlays, lifecycle events and native access when you need it. A community library, independent of Neshan.

The online playground lets you try clusters, weighted heatmaps, 3D layers, editable GeoJSON, styles, markers, point picking and distance measurement. Enter your own web key with sajadjanat.github.io allowed in Neshan. The key stays in page memory; it is not saved or included in links. Error simulations make no network requests. Demo guide.

One library, two engines

Build withWhat you get
Vue modelsKeep center and zoom in sync; pick a point and persist dragged markers.
Live vector layersTraffic and POI on MapLibre, five map styles and reactive visibility.
Your geographyMarkers, text popups, tooltips, lines, polygons, meter-radius circles and GeoJSON.
Spatial analysisAutomatic point clusters, weighted heatmaps, custom sources/layers and 3D extrusion.
Application controlsFly to a place, frame markers, request browser location and access the native renderer.
Lifecycle handlingLazy SDK initialization, loading/error slots, retry, automatic resize and cleanup.

Find your way

I want to…Read
Display my first mapQuick start
Pick an address, add places or draw a service areaFeature guide
Copy a complete Vue componentRunnable examples
Check a prop, event, slot or methodAPI reference
Use Nuxt, native APIs or migrate from 1.xIntegration guide
Diagnose blank tiles or missing trafficTroubleshooting
Cluster points, draw a heatmap or add advanced layersAdvanced MapLibre guide

Choose an engine

NeshanMapLibreNeshanMapLeaflet
RendererVector / WebGLRaster / DOM
Renderer / connectorMapLibre 6.14.0 / adapted Neshan connectorLeaflet 1.9.4 / Neshan SDK 1.0.8
Live traffic / POISupported with compatible stylesNo longer updated by Neshan; ignored with warnings
StylesLight, dark, monochrome light/dark, pastel; custom version-8 styledreamy, standard-night; vendor-supported tile URLs
Component coordinates[latitude, longitude][latitude, longitude]
Native optionsMapLibre optionsLeaflet options
Use caseNew vector maps and live layersExisting Leaflet applications/plugins

Quick start

Requires Vue 3.5.43+. Tooling: Node 22.12+ within 22.x, or Node 24+. MapLibre needs a modern browser with WebGL and DecompressionStream.

Install from npm:

npm install vue-neshan-maps@^2.3.0

Create a domain-allowed web key in the Neshan dashboard. For Vite, put it in your ignored .env.local:

VITE_NESHAN_WEB_KEY=web.YOUR_DOMAIN_ALLOWED_KEY

Web keys are visible in browsers by design; restrict their allowed domains. Service keys belong on your server, never in public Vue environment variables. Search, geocoding and routing services are not included in this package.

<script setup lang="ts">
import { ref } from 'vue'
import { NeshanMapLibre, MAPLIBRE_STYLES, type Coordinates } from 'vue-neshan-maps'
import 'vue-neshan-maps/style.css'

const webKey = import.meta.env.VITE_NESHAN_WEB_KEY
const center = ref<Coordinates>([35.6997, 51.338])
const zoom = ref(13)
const traffic = ref(true)
const poi = ref(true)
</script>

<template>
  <NeshanMapLibre
    v-model="center" v-model:zoom="zoom" :api-key="webKey"
    :map-style="MAPLIBRE_STYLES.light" :traffic="traffic" :poi="poi"
    :pick-on-click="true" :styles="{ height: '500px' }"
  />
  <label><input v-model="traffic" type="checkbox">Traffic</label>
  <label><input v-model="poi" type="checkbox">Places</label>
  <p>{{ center[0] }}, {{ center[1] }} · zoom {{ zoom }}</p>
</template>

Import package CSS once. Both components default to 100% width and a 450px height. centerMarker is a fixed visual center pin, separate from markers.

Leaflet

Use the same center, zoom and webKey setup, import NeshanMapLeaflet instead, then use:

<NeshanMapLeaflet
  v-model="center" v-model:zoom="zoom" :api-key="webKey"
  :options="{ maptype: 'dreamy' }" :pick-on-click="true"
  :styles="{ height: '500px' }"
/>

Try the Leaflet engine in the live demo.

Coordinate order

APIOrderExample
Component model, marker/shape positions, component flyToLatitude, longitude[35.6997, 51.338]
GeoJSON, native MapLibre APIs, MapLibre fitBoundsLongitude, latitude[51.338, 35.6997]
Leaflet fitBoundsLatitude, longitude[[35.69, 51.32], [35.73, 51.39]]

Nuxt 4

Register the module to add both components, composables and package CSS automatically:

export default defineNuxtConfig({
  modules: ['vue-neshan-maps/nuxt'],
  runtimeConfig: { public: { neshanWebKey: '' } },
})

Set NUXT_PUBLIC_NESHAN_WEB_KEY and read it with useRuntimeConfig(). Complete Nuxt 4 guide: module, manual plugin, SSR and production config →

Markers and overlays

For point picking, editable markers, shapes, GeoJSON, native controls and status handling, follow the feature guide. Each section explains what your application owns and what the component updates.

Use unique, stable IDs and persist dragging in application state:

import { ref } from 'vue'
import type { Coordinates, MapLibreMarker, MapCircle, MapPolygon, MapPolyline } from 'vue-neshan-maps'

const markers = ref<MapLibreMarker[]>([
  { id: 'azadi', position: [35.6997, 51.338], popup: 'Azadi Square',
    tooltip: 'Drag to move', options: { draggable: true } },
])
function saveMarker(marker: MapLibreMarker, position: Coordinates) {
  markers.value = markers.value.map(item => item.id === marker.id ? { ...item, position } : item)
}
const circles: MapCircle[] = [
  { id: 'area', center: [35.6997, 51.338], radius: 500, options: { color: '#0f766e', fillOpacity: 0.15 } },
]
const polylines: MapPolyline[] = [
  { id: 'connection', positions: [[35.6997, 51.338], [35.7448, 51.3753]], options: { weight: 4 } },
]
const polygons: MapPolygon[] = [
  { id: 'zone', positions: [[35.70, 51.34], [35.72, 51.34], [35.72, 51.37]], options: { color: '#7c3aed' } },
]

Pass :markers="markers", :circles="circles", :polylines="polylines", :polygons="polygons" and @marker-dragend="saveMarker" to the component. For Leaflet use the MapMarker type. Popup/tooltip strings are plain text. A polyline displays supplied coordinates; it does not calculate a road route.

MapLibre supports native geojsonLayers and geojsonOptions. Shape props support a documented subset of Leaflet-like paint options, not arbitrary Leaflet plugin options. See API details and GeoJSON examples.

Map styles in practice

Light and dark styles use the same component state. Markers and custom overlays survive style changes.

Dark Neshan MapLibre map with live traffic and custom overlays

These screenshots are unedited captures of the live bilingual demo. POI density and traffic detail depend on the viewport, zoom, vendor style and available data.

Live demo gallery: clustering, heatmap and 3DPoint clusters with English controlsWeighted heatmap with English controls3D layer with English height and opacity controls

Events, methods and slots

PurposeAPI
Native accessready → { map, maplibre } or { map, L }; destroy clears consumer refs
Modelsupdate:modelValue, update:zoom; compatibility latitude, longitude
Interactionclick, moveend, zoomend, marker-click, marker-dragend
Statuserror, warning, tiles-loading, tiles-loaded, tile-error
Locationlocation-found, location-error
MapLibre live layerslive-layers → flags and matched layer IDs
Ref methodsretry, fitToMarkers, flyTo, fitBounds, invalidateSize, locate
MapLibre extrasopenPopup(id), getDiagnostics()
Slotsloading, error with { error, retry }, tile-error with { state }, center-marker, default native context

ready confirms initialization, not tile authorization. MapLibre counts source readiness events; Leaflet counts raster tile results, so counts are not comparable. getDiagnostics() reports rendered MapLibre features in the current viewport/zoom, not totals for the dataset.

Use useNeshanMapLibre() or useNeshanMap() in setup and wire both @ready="onReady" and @destroy="onDestroy". The component owns native map disposal. Full payloads, defaults and signatures →

Migration and runtime

  • From 1.x: Vue is a peer dependency; import package CSS; replace UMD scripts with ESM/CommonJS. Leaflet options.key, options.center, latitude and longitude remain supported. Prefer explicit key and models.
  • SSR / Nuxt: imports are SSR-safe; SDK initialization happens on mount. Import CSS globally. <ClientOnly> is optional. Do not initialize the renderer on the server.
  • Style updates: MapLibre retains DOM markers/popups and restores custom layers/live visibility after style.load. Leaflet uses setMapType without replacing the map.
  • Native options: remount with Vue :key to change constructor-only options. Web-key changes rebuild the map; refresh native references on ready.
  • Multiple maps: concurrent Neshan MapLibre maps must use the same page-global key. Leaflet uses a global SDK namespace; mixing versions is unsupported.
  • Location: HTTPS or localhost and browser permission are required. Location is only requested when your app calls locate().
  • CSP/custom hosting: Leaflet supports sdk.scriptUrl, styleUrl, timeout, nonce. MapLibre uses the installed SDK; allow its style/tile/font/worker resources in your CSP. See the API reference.

Troubleshooting

SymptomCheck
Blank mapCSS, height, web key, allowed domain, quota, network; WebGL/DecompressionStream for MapLibre
Missing traffic/POIUse MapLibre and a style containing Neshan traffic / points_source sources/layers; inspect warning and live-layers
Marker jumps back after dragPersist marker-dragend in your markers array
Wrong locationLatitude-first props versus longitude-first GeoJSON/native MapLibre
Location failureHTTPS/localhost, permission and device availability; handle location-error
Partial Leaflet deliverySupplemental vendor labels may return HTTP 204 without an image; inspect tile events separately from base-map delivery

Traffic tiles are re-requested every 60 seconds while enabled; Neshan controls caching/freshness. Vendor availability and quotas are outside this wrapper. Keep required Neshan/OpenStreetMap attribution visible.

Patched renderer: 2.2.0 replaces the vulnerable bundled engine with MapLibre GL JS 6.14.0. Neshan tile/RTL/branding compatibility comes from an adapter-only ISC fork of SDK 5.24.4, maintained by this community package. Attribution sanitization and text popups remain. See security, CSP and compatibility. The release audit reports zero known vulnerabilities; this is a dependency snapshot, not a guarantee about application code.

Playground and contributing

git clone https://github.com/sajadjanat/vue-neshan-maps.git vue-neshan-maps
cd vue-neshan-maps
npm ci
npm run dev

Enter a web key in the playground, or copy .env.example to .env.local. The runtime form does not save the key. Never commit environment files containing credentials.

npm run typecheck
npm test
npm run test:sdk
npm run build
npm run build:demo
npm run test:package

Validation includes 55 unit/contract tests, 10 tests using the actual pinned Leaflet SDK, strict installed-package TypeScript checks and real-map browser checks. Automated SDK tests stub image/quota networking and simulate location callbacks; actual device GPS was not requested. CI covers Node 22 and 24.

For issues, include engine/package/browser versions, a small reproduction and sanitized errors. Never include service keys or account tokens.

Changelog · Releases · Report an issue

Official references

Neshan MapLibre · Neshan Leaflet · Vue · Leaflet API

MIT © Sajad Abasi. License.

Clusters, heatmap and advanced layers

Version 2.2 adds automatic point clustering, weighted heatmaps, declarative native sources/layers (including 3D extrusion), structured key/quota errors and incremental geometry updates. Complete guide and examples · فارسی.

Vazir font

Vazir 30.1.0 regular and bold are bundled as self-hosted WOFF2 files. The original SIL OFL license and author credits are included in fonts. No font CDN is required.

import 'vue-neshan-maps/fonts/vazir.css'
<div class="neshan-font-vazir" lang="fa" dir="rtl">
  <!-- Your map, popups and Persian controls -->
</div>

The class styles the interface and HTML popups. Vector labels use the Neshan style glyphs; raster labels are part of tile images. This font does not replace either. GitHub controls its Markdown typography; Persian screenshots are captured with Vazir.

Dependency security status and the Nuxt example fix