Skip to content
Vue Neshan MapsDeveloper documentation

Quick start

Install, import CSS, and render a complete Vue component.

4 min readVue 3 / Nuxt 4Live example included
On this pageRequirementsInstallConfigure the keyComplete Vue exampleVerify the result
Interactive exampleMapLibre

Run on a real Neshan map

Bring a domain-allowed web key. Sample geometry; real basemap.

Allow sajadjanat.github.io. Your key stays in memory and is excluded from copied code.

Requirements#

Use Vue 3.5.43 or newer. Supported tooling is Node 22.12+ within 22.x, or Node 24+. MapLibre requires WebGL and DecompressionStream. Nuxt 4 has stricter Node patch requirements; see its dedicated guide.

Install#

bash
npm install vue-neshan-maps

Configure the key#

Create a web key in the Neshan dashboard and allow your development and production domains. In Vite, keep the value in an ignored environment file:

dotenv
VITE_NESHAN_WEB_KEY=web.YOUR_DOMAIN_ALLOWED_KEY

Complete Vue example#

The example below combines the main capabilities in one component: live layers, editable places, a service area, a style switch, a point picker and application-owned controls. No control panel or sidebar is injected by the package. Build the interface that fits your app.

Set VITE_NESHAN_WEB_KEY in .env.local and restart Vite after changing environment variables.

vue
<script setup lang="ts">
import { ref } from 'vue'
import {
  NeshanMapLibre, MAPLIBRE_STYLES,
  type Coordinates, type MapLibreMarker, type MapCircle,
  type MapPolygon, type MapPolyline, type TileState,
} 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)
const dark = ref(false)
const picking = ref(false)
const mapRef = ref<InstanceType<typeof NeshanMapLibre>>()
const initialized = ref(false)
const resourceStatus = ref('idle')
const message = ref('')
const markers = ref<MapLibreMarker[]>([
  { id: 'azadi', position: [35.6997, 51.338], popup: 'Azadi Square',
    tooltip: 'Drag to edit', options: { color: '#087f8c', draggable: true } },
  { id: 'laleh', position: [35.711, 51.39], popup: 'Laleh Park' },
])
const circles: MapCircle[] = [
  { id: 'service', center: [35.711, 51.39], radius: 620,
    options: { color: '#087f8c', fillColor: '#1eb5aa', fillOpacity: 0.15 } },
]
const polygons: MapPolygon[] = [
  { id: 'area', positions: [[35.721, 51.346], [35.728, 51.354], [35.722, 51.365]],
    options: { color: '#b66a20', fillColor: '#e3ad58', fillOpacity: 0.2 } },
]
const polylines: MapPolyline[] = [
  { id: 'connection', positions: [[35.6997, 51.338], [35.711, 51.39]],
    options: { color: '#087f8c', weight: 3, dashArray: '6,5' } },
]
function saveMarker(marker: MapLibreMarker, position: Coordinates) {
  markers.value = markers.value.map(item => item.id === marker.id ? { ...item, position } : item)
}
function resources(state: TileState) { resourceStatus.value = state.status }
function showError(error: Error) { message.value = error.message }
function locationError(error: { code: number; message: string }) { message.value = error.message }
function locate() { mapRef.value?.locate({ setView: true, maxZoom: 16 }) }
function inspect() {
  const report = mapRef.value?.getDiagnostics()
  message.value = report
    ? `${report.markers} markers; ${report.trafficFeatures} traffic features; ${report.poiFeatures} POI features in this view`
    : 'Map is not ready yet'
}
</script>

<template>
  <section aria-label="Map controls">
    <label><input v-model="traffic" type="checkbox">Traffic</label>
    <label><input v-model="poi" type="checkbox">Places</label>
    <label><input v-model="dark" type="checkbox">Dark style</label>
    <label><input v-model="picking" type="checkbox">Pick a point</label>
    <button :disabled="!initialized" @click="mapRef?.fitToMarkers()">Show all places</button>
    <button :disabled="!initialized" @click="mapRef?.flyTo([35.711, 51.39], 15)">Laleh Park</button>
    <button :disabled="!initialized" @click="mapRef?.openPopup('azadi')">Open Azadi</button>
    <button :disabled="!initialized" @click="locate">My location</button>
    <button :disabled="!initialized" @click="inspect">Inspect layers</button>
  </section>
  <NeshanMapLibre
    ref="mapRef" v-model="center" v-model:zoom="zoom" :api-key="webKey"
    :map-style="dark ? MAPLIBRE_STYLES.dark : MAPLIBRE_STYLES.light"
    :traffic="traffic" :poi="poi" :pick-on-click="picking"
    :center-marker="picking" :markers="markers"
    :circles="circles" :polygons="polygons" :polylines="polylines"
    :styles="{ height: '520px' }" aria-label="Tehran places and service area"
    @ready="initialized = true" @destroy="initialized = false"
    @marker-dragend="saveMarker" @tiles-loading="resources"
    @tiles-loaded="resources" @tile-error="resources"
    @warning="message = $event" @error="showError"
    @location-error="locationError"
  >
    <template #loading><p role="status">Preparing the map…</p></template>
    <template #error="{ error, retry }">
      <p role="alert">{{ error.message }} <button @click="retry">Try again</button></p>
    </template>
    <template #tile-error="{ state }">
      <p role="status">Some resources failed ({{ state.failed }}). Check the key and network.</p>
    </template>
  </NeshanMapLibre>
  <p>Selected center: {{ center[0].toFixed(5) }}, {{ center[1].toFixed(5) }} · Zoom: {{ zoom }}</p>
  <p role="status">Map resources: {{ resourceStatus }}. {{ message }}</p>
</template>

Verify the result#

You should see the basemap and attribution. The center pin appears when centerMarker is enabled. An empty map usually means missing CSS, zero container height, a key/domain error or unavailable WebGL. Check the browser resource errors, not only the ready event.

Independent community library. Map services and attribution belong to Neshan and OpenStreetMap. · Sajad Janat · MIT