schlunsen/nuxt-leaflet

Maps in nuxts static mode break

duktiga-havet opened this issue · 10 comments

Hi!
I've been banging my head against this the last few weeks. Maps work great in universal mode but when I run nuxt generate they fall apart - map tiles are spread out on the page instead of being in element, see the screenshot below.

The implementation worked before but broke in an update(?) and I haven't been able to pin down which package yet. I get the same result with google maps in Nuxt and leaflet and google maps in Gridsome so it seems to be a pretty general issue with maps and static.

Has anyone else run into this? Any ideas on how to debug?

image

try adding around the map

I have the very same issue. @schlunsen what do you mean with "try adding around the map" exactly? Here's my map component

<template>
  <div>
    <client-only>
      <loading class="z-999" :active="loadingMapData" :can-cancel="true" :is-full-page="false" :color="spinnerColor" />
      <l-map class="w-full h-3/4" :zoom="zoom" :center="center" :options="options" @click="clearDescription">
        <shop-card v-if="shop" :shop="shop" class="absolute left-0 bottom-0 z-999" />
        <l-tile-layer :url="tilesUrl" />
        <shop-marker v-for="shop in shops" :key="shop.id" :shop="shop" @display-description="displayDescription" />
      </l-map>
    </client-only>
  </div>
</template>

<script>
import ClientOnly from 'vue-client-only'
import Loading from 'vue-loading-overlay'
import ShopCard from '~/components/Map/ShopCard'
import ShopMarker from '~/components/Map/ShopMarker'
import shops from '~graphql/shops'

export default {
  apollo: {
    shops: {
      query: shops
    }
  },
  components: {
    ShopCard,
    ShopMarker,
    ClientOnly,
    Loading
  },
  props: {
    center: {
      type: Array,
      required: true
    },
    zoom: {
      type: Number,
      required: true
    },
    tilesUrl: {
      type: String,
      // cf. https://sosm.ch/projects/tile-service/
      default: 'https://tile.osm.ch/switzerland/{z}/{x}/{y}.png'
    }
  },
  data: () => ({
    shop: undefined,
    spinnerColor: '#e78000ff'
  }),
  computed: {
    loadingMapData () {
      return this.$apollo.queries.shops.loading
    },
    options () {
      return {
        gestureHandling: true,
        gestureHandlingOptions: {
          text: {
            touch: this.$i18n.t('gestureHandling.touch'),
            scroll: this.$i18n.t('gestureHandling.scroll'),
            scrollMac: this.$i18n.t('gestureHandling.scrollMac')
          },
          duration: 2000
        },
        zoomControl: false
      }
    }
  },
  methods: {
    displayDescription (id) {
      this.shop = this.shops.find(item => item.id === id)
    },
    clearDescription () {
      this.shop = undefined
    }
  }
}
</script>

<style src="~/assets/css/tailwind.css"></style>
<style src="vue-loading-overlay/dist/vue-loading.css"></style>
<style src="leaflet/dist/leaflet.css"></style>
<style src="leaflet-gesture-handling/dist/leaflet-gesture-handling.css"></style>
<style>
/* .leaflet-tile-pane {
  -webkit-filter: grayscale(100%);
  filter: grayscale(100%);
} */
</style>

and here's my nuxt config:

import i18n from './i18n'
const path = require('path')
const pkg = require('./package')

module.exports = {
  mode: 'universal',

  env: {
    GRAPHQL_API: process.env.GRAPHQL_API || 'http://localhost:8080/v1/graphql/'
  },

  /*
  ** Headers of the page
  */
  head: {
    title: pkg.name,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: pkg.description }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },

  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#fff' },

  /*
  ** Global CSS
  */
  css: [
  ],

  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
    { src: '~plugins/errorHandling.js' },
    { src: '~plugins/leaflet.js', mode: 'client' }
  ],

  buildModules: [
    '@nuxtjs/tailwindcss'
  ],

  /*
  ** Nuxt.js modules
  */
  modules: ['@nuxtjs/apollo', 'nuxt-purgecss', ['nuxt-i18n', i18n]],

  purgeCSS: {
    paths: [
      'i18n/**/*.js'
    ]
  },

  // Give apollo module options
  apollo: {
    tokenExpires: 10, // optional, default: 7 (days)
    includeNodeModules: true, // optional, default: false (this includes graphql-tag for node_modules folder)
    authenticationType: 'Basic', // optional, default: 'Bearer'
    // optional
    // errorHandler (error) {
    //   console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
    // },
    // required
    clientConfigs: {
      default: '~/apollo/clientConfig.js'
    }
  },

  /*
  ** Build configuration
  */
  build: {
    extractCSS: true,
    postcss: {
      // Add plugin names as key and arguments as value
      // Install them before as dependencies with npm or yarn
      plugins: {
        // Disable a plugin by passing false as value
        // 'postcss-url': false,
        // 'postcss-nested': {},
        // 'postcss-responsive-type': {},
        // 'postcss-hexrgba': {}
      },
      preset: {
        // Change the postcss-preset-env settings
        autoprefixer: {
          grid: true
        }
      }
    },

    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
      config.resolve.alias['~graphql'] = path.resolve(__dirname, '../../shared/graphql/')
    }
  }
}

and here's my leaflet.js plugin:

import Vue from 'vue'
import { LMap, LMarker, LTileLayer } from 'vue2-leaflet'
// eslint-disable-next-line no-unused-vars
import { GestureHandling } from 'leaflet-gesture-handling'

import { Icon } from 'leaflet'
delete Icon.Default.prototype._getIconUrl

Icon.Default.mergeOptions({
  iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
  iconUrl: require('leaflet/dist/images/marker-icon.png'),
  shadowUrl: require('leaflet/dist/images/marker-shadow.png')
})

Vue.component('l-map', LMap)
Vue.component('l-tile-layer', LTileLayer)
Vue.component('l-marker', LMarker)

I'm not using your plugin directly. The bug occurs also without using nuxt-leaflet. I guess it's misuse of the nuxt framework. Does anyone have a clue how to make it working?

I'm using:

  • leaflet@1.6.0
  • nuxt@2.11.0
  • vue-client-only@2.0.0

I've tried to

yarn add vue-no-ssr

and use the following map component:

<template>
  <div>
    <div id="map-wrap" style="height: 100vh; width: 100%;">
      <no-ssr>
        <l-map :zoom="13" :center="[47.413220, -1.219482]">
          <l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" />
          <l-marker :lat-lng="[47.413220, -1.219482]" />
        </l-map>
      </no-ssr>
    </div>
  </div>
</template>

Doesn't work either.

If I compile my app as an SPA and don't include

<style src="leaflet/dist/leaflet.css"></style>

in the vue component file, then I get the same issue. It's like the css is not loaded in the static mode if I include the css in the component. If I clone your example and generate the html from it, I see the leaflet css in the generated index file. I don't see that in the html of my project. What can I be missing?

Removing

build.extractCSS: true

from nuxt.config.js works, but then I can't use webpack configuration.

If was finally able to fix it here.

Sorry I meant wrap it "client-only" tags around the map.

<client-only>
</client-only>

Glad you got it fixed. Please post your fix if it's to any interest for others. Thanks alot!

So in my case it was a misuse of the purgeCSS. I replaced

purgeCSS: {
    paths: [
      'i18n/**/*.js'
    ]
  },

with

purgeCSS: {
    paths: [
      'i18n/**/*.js'
    ],
    whitelistPatterns: [/leaflet/, /marker/]
  },

in nuxt.config.js.

@zadigus amazing! so happy you figured this out!

Maybe this could be added to the docs?