Using Leaflet with SvelteKit
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';
let mapElement;
let map;
onMount(async () => {
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:
View Comments
A case for the mediocre developer
Software engineers today face a lot of difficulties in their jobs. The available...
Oliver
AuthorThanks for sharing! I could also load the css from the installed NodeJS module using
@import 'leaflet/dist/leaflet.css';
Stanislav Khromov
AuthorThanks for the comment Oliver, added your suggestion in the post!
Bray
Authorgreat job! simple and straight to the point!
gd
AuthorGreat 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
Stanislav Khromov
AuthorHi 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
gd
AuthorThanks 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
Stanislav Khromov
AuthorHi 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
gd
AuthorThanks 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?
Stanislav Khromov
AuthorIf 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!
gd
AuthorHi 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
gd
AuthorHey 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?
gd
AuthorIt’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
Huaso
AuthorHello 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.
Lucian
AuthorHello, why did you use async in onDestroy?
Stanislav Khromov
AuthorHi! 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.
delanyo
AuthorThat’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?
Mugu
AuthorDoes leaflet marker clustering work with sveltekit and typescript ?
Stanislav Khromov
AuthorHi! Yes, it should work! If you’re having any issues, feel free to drop by the Svelte Discord, there is a help channel: https://svelte.dev/chat