Quick start
Install, import CSS, and render a complete Vue component.
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#
npm install vue-neshan-mapsConfigure 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:
VITE_NESHAN_WEB_KEY=web.YOUR_DOMAIN_ALLOWED_KEYComplete 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.
<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.