Skip to content

Nuxt 3 Quickstart

Install

bash
npm install maplibre-gl

Component (client-only)

MapLibre is browser-only. Use <ClientOnly> or import in onMounted.

vue
<!-- components/MapView.client.vue -->
<!-- The .client.vue suffix makes Nuxt skip SSR for this file. -->

<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue';
import maplibregl, { type Map } from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

const config = useRuntimeConfig();
const el = ref<HTMLDivElement>();
let map: Map | null = null;

onMounted(() => {
  if (!el.value) return;
  map = new maplibregl.Map({
    container: el.value,
    style: `https://api.mapsfordevs.com/styles/standard.json?key=${config.public.mfdPubKey}`,
    center: [28.04, -26.20],
    zoom: 11
  });
});

onBeforeUnmount(() => { map?.remove(); map = null; });
</script>

<template><div ref="el" class="w-full h-screen" /></template>

nuxt.config.ts

ts
export default defineNuxtConfig({
  runtimeConfig: {
    mfdSrvKey:    process.env.MFD_SRV_KEY,           // server only
    mfdSrvSecret: process.env.MFD_SRV_SECRET,
    public: {
      mfdPubKey: process.env.NUXT_PUBLIC_MFD_PUB_KEY // browser-safe
    }
  }
});

Server route — tile proxy

ts
// server/api/tiles/[z]/[x]/[y].pbf.get.ts
import { createHmac } from 'node:crypto';

export default defineEventHandler(async (event) => {
  const { z, x, y } = getRouterParams(event);
  const config      = useRuntimeConfig();
  const path        = `/tiles/${z}/${x}/${y}.pbf`;
  const ts          = String(Math.floor(Date.now() / 1000));
  const sig         = createHmac('sha256', config.mfdSrvSecret)
                        .update(`GET\n${path}\n${ts}`).digest('hex');

  const upstream = await $fetch.raw<ArrayBuffer>(`https://tiles.mapsfordevs.com${path}`, {
    headers: {
      Authorization: `Bearer ${config.mfdSrvKey}`,
      'X-MFD-Timestamp': ts,
      'X-MFD-Signature': sig
    },
    responseType: 'arrayBuffer'
  });

  setHeader(event, 'Content-Type', 'application/x-protobuf');
  setHeader(event, 'Cache-Control', 'public, max-age=86400');
  return Buffer.from(upstream._data!);
});

Geocoding composable

ts
// composables/useGeocode.ts
export function useGeocode() {
  const config  = useRuntimeConfig();
  const results = ref<any[]>([]);
  const loading = ref(false);

  async function search(q: string) {
    loading.value = true;
    try {
      const body = await $fetch<{ ok: boolean; items: any[]; error?: string }>(
        'https://api.mapsfordevs.com/geocode/forward',
        {
          headers: { Authorization: `Bearer ${config.public.mfdPubKey}` },
          query:   { q, limit: 5 }
        }
      );
      if (body.ok) results.value = body.items;
    } finally {
      loading.value = false;
    }
  }

  return { results, loading, search };
}