Search code examples
javascriptvue.jsvuejs3nuxt.jsnuxt3.js

A proper way to install leaflet in Nuxt/Vue


I'm trying to install leaflet in my Nuxt 3 project without the use of wrappers like @nuxtjs/leaflet, however it says 'Could not find a declaration file for module 'leaflet', although I installed the leaflet package from npm.

//template
<div id="map"></div>

//script
<script setup lang="ts">
import * as L from "leaflet";

onMounted(() => {
    const map = L.map("map").setView([34.299074, -118.45949], 13);
    L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
        maxZoom: 19,
        attribution:
            '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
    }).addTo(map);
    L.marker([34.299074, -118.45949]).addTo(map);
});
</script>

Then I add a css file in nuxt.config.ts:

css: [
    "leaflet/dist/leaflet.css",
],

All I get right now is the empty map with zoom in/out buttons and the title 'Leaflet | © OpenStreetMap' in the bottom-right corner.

Here's the reproduction: https://stackblitz.com/edit/nuxt-starter-jcj5xn?file=app%2Fpages%2Findex.vue,app%2Fapp.vue,app%2Fpages%2Fevents.vue. If you click on the link 'Go to Events' the empty map opens up. If you reload the page you get the error 'window' is not defined. In my actual project I can't even drag the map and there's no marker as well. Also, it's been saying 'Attempted to load an infinite number of tiles', although https://tile.openstreetmap.org/{z}/{x}/{y}.png is a correct tileLayer, according to the official documentation leafletjs.com/examples/quick-start.

Looking for a possible solution.


Solution

  • Popular up-to-date packages that don't contain TypeScript types are coupled with respective packages from @types scope as a rule of thumb. In this case @types/leaflet should be installed together with leaflet.

    The error that occurs on page reload is specific to Nuxt server. leaflet module is client-only and unsafely accesses window global variable. Whether the module is evaluated or not or on server side is at the discretion of Nuxt's build tool. To avoid this situation, the module shouldn't be imported on server side:

    onMounted(async () => {
      const L = await import('leaflet');
      ...
    

    The problem with map tiles not being shown is specific to a particular sandbox. It can be seen in devtools that the map is rendered but map tile requests are marked as "blocked":

    https://tile.openstreetmap.org/13/1400/3263.png

    https://tile.openstreetmap.org/13/1400/3264.png

    ...

    With the reason explained in request details and "issues" tab:

    not-set Cross-Origin-Resource-Policy:

    Because your site has the Cross-Origin Embedder Policy (COEP) enabled, each resource must specify a suitable Cross-Origin Resource Policy (CORP). This behavior prevents a document from loading cross-origin resources which don’t explicitly grant permission to be loaded.

    To solve this, add the following to the resource’ response header:

    Cross-Origin-Resource-Policy: same-site if the resource and your site are served from the same site.

    Cross-Origin-Resource-Policy: cross-origin if the resource is served from another location than your website. ⚠️If you set this header, any website can embed this resource.

    This is caused by the demo running in a preview box, with cross-origin limitations being applied to an iframe. This is fixed with a preview being opened in a separate window, which can work unreliably on Stackblitz, or running Nuxt locally.