Stanislav Khromov

Having just started on a new SvelteKit project, I was tasked with implementing a Leaflet component.

Using Leaflet in SvelteKit is a bit tricky due to its dependency on the window object and the way SvelteKit builds its production bundle, but it is fully possible, so let’s do that in this post. We’re going to assume that you have a working SvelteKit app. If not, follow the official guide first!

Let’s start out by installing leaflet:

npm i -D leaflet

Now we can create a simple Svelte component based on the Leaflet Quick Start guide!

Create the file src/lib/LeafletMap.svelte

<script>
    import { onMount, onDestroy } from 'svelte';
    import { browser } from '$app/environment';

    let mapElement;
    let map;

    onMount(async () => {
        if(browser) {
            const leaflet = await import('leaflet');

            map = leaflet.map(mapElement).setView([51.505, -0.09], 13);

            leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
            }).addTo(map);

            leaflet.marker([51.5, -0.09]).addTo(map)
                .bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
                .openPopup();
        }
    });

    onDestroy(async () => {
        if(map) {
            console.log('Unloading Leaflet map.');
            map.remove();
        }
    });
</script>


<main>
    <div bind:this={mapElement}></div>
</main>

<style>
    @import 'leaflet/dist/leaflet.css';
    main div {
        height: 800px;
    }
</style>

Finally, add your new component to your src/routes/index.svelte file:

<script>
	import LeafletMap from '$lib/LeafletMap.svelte';
</script>

<main>
	<LeafletMap />
</main>

Now it’s time to start the dev server with npm run dev and visit http://localhost:3000/ to marvel at the results:

Full-stack impostor syndrome sufferer & Software Engineer at Schibsted Media Group

View Comments

  • OliverOliver

    Author Reply

    Thanks for sharing! I could also load the css from the installed NodeJS module using

    @import 'leaflet/dist/leaflet.css';


  • BrayBray

    Author Reply

    great job! simple and straight to the point!


  • gdgd

    Author Reply

    Great post! I’m trying to make working leaflet.markercluster based on your example.
    How would you import it in onMount? Here is a basic example:
    https://jsbin.com/fimaxap/2/edit?html,js,output


    • Hi gd!

      There’s nothing special about markercluster or any Leaflet library, you can use them normally inside onMount. The only quirk is how Leaflet plugins are loaded and how that interacts with ES6 imports, but that’s not SvelteKit-specific.

      I made an example with normal Svelte, as there is no REPL playground for SvelteKit, but it should be the same except for the browser check in SK:
      https://svelte.dev/repl/761fc7956ca3499888545613f54a9146?version=3.53.1


      • gdgd

        Author

        Thanks Stanislav for the answer and the REPL! A bit before receiving your reply, I finally made it working in SK with onMount and if (browser). Now the only tricky thing with onMount is that I have reactive data that generates the markers. And the reactive statement $: doesn’t work in onMount… Any idea how to pass reactive data into that? Many thanks in advance


      • Hi gd,

        You are right, you can not do reactive things in onMount, as it runs only once. What you need to do is initialize the map in onMount and then create a reactive statement outside onMount that only triggers if you have mounted. I made an example below that allows you to remove points from the map with a button. The actual removal of markers is something Leaflet-specific. Every time the markers data changes, we remove the old markers group from the leaflet map and add a new one with the current marker data. You can see it here:
        https://svelte.dev/repl/521971715dde45c8b11eee45a48400b8?version=3.53.1

        PS. To make things easier for you in SvelteKit, you can disable SSR for a specific route as per the documentation below. If you don’t care about SSR for SEO purposes, this can make things easier for you as you can just pretend it’s a normal client-side rendered Svelte page:
        https://kit.svelte.dev/docs/page-options#ssr


  • gdgd

    Author Reply

    Thanks a lot Stanislav, this is a brilliant example with very clear indications!
    Indeed your example is working fine in sveltekit with SSR=false. I’m a bit afraid to go only client-side because I’m also fetching data from an API to render the markers. Fetching the API and rendering the map, all that on the client could make the page is a bit slow though?
    Someone on discord told me to use “use:action” instead of onMount to keep the data reactive but I don’t know how leaflet is gonna to like it or not… 😉 What do you think?


    • If your Leaflet map is just a small component in a larger surrounding page where you want the benefits of SSR, then you should not disable it in SvelteKit, since that would disable it for the whole page. As you mentioned you also can’t benefit from the server side load() function in SvelteKit.

      Regarding: use:action – yes, you could refactor my example code as an action. Actions are kind of like React hooks or HoCs – they let you abstract away some complicated task and make the code reusable.

      If you rewrote the earlier example code as an action the interface could be something like this:

      <div use:leafletWithMarkers={initialMarkerData} on:removeMarker={(m) => { /* ... */ }} />

      Using on:yourEventName you can specify an arbitrary number of events so that you can communicate between your component and the action.

      Hope that your project is going well!


  • gdgd

    Author Reply

    Hi Stanislav, I’m reworking on my map project following your last REPL example. I just saw that all the title in all markers are the same. Any idea what is wrong? Many thanks in advance


  • gdgd

    Author Reply

    Hey Stanislav, I just have tried to “npm run build” the project and I got an error:
    ReferenceError: window is not defined
    at /Users/gd/Developer/projets.archi/node_modules/leaflet/dist/leaflet-src.js:230:19
    at /Users/gd/Developer/projets.archi/node_modules/leaflet/dist/leaflet-src.js:7:66
    at Object. (/Users/gd/Developer/projets.archi/node_modules/leaflet/dist/leaflet-src.js:10:3)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Module._load (node:internal/modules/cjs/loader:958:12)
    at ModuleWrap. (node:internal/modules/esm/translators:169:29)
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
    But in dev mode, it’s working!
    Do you have the same error when you build your project?


    • gdgd

      Author Reply

      It’s probably due to the leaflet code in the reactive statement $ from your last REPL https://svelte.dev/repl/521971715dde45c8b11eee45a48400b8?version=3.53.1
      Any idea how to make it working for building in SvelteKit? It’s working in dev mode
      Thank you


      • HuasoHuaso

        Author

        Hello there gd, did you manage to make it work? i was working on dev just find an one moment to another the window reference got bad, it dosent work anymore lol, not sure why.


  • LucianLucian

    Author Reply

    Hello, why did you use async in onDestroy?


    • Hi! No reason, since there are no async function calls in onDestroy, you can remove “async” from the function signature. Svelte supports both sync and async functions in onMount/onDestroy.


  • delanyodelanyo

    Author Reply

    That’s cool. But I guess you can import the leaflet directly in onMount without checking browser?

    Because onMount runs only in the browser? When the component has mounted in the browser?


  • MuguMugu

    Author Reply

    Does leaflet marker clustering work with sveltekit and typescript ?


Next Post