Ragnarök: Rebuilding Our Maps
The Nine Realms
Maps are everywhere in our products. Almost every screen shows something on a map: zones, alerts, devices, the trace of a phone across a city. For years all of this has been drawn by Leaflet , a small and reliable JavaScript mapping library. Leaflet served us well — but over the years it grew roots into every corner of the front-end, and those roots became hard to move.
This project was part of the 17th Intersec Hackathon (0x11), in December 2025 — a solo effort, with two days on the clock. The goal was not to add a feature: it was to prove that we could rebuild the foundations of our map component so that the rendering engine becomes an interchangeable part — that we could, one day, swap Leaflet for something else without setting the rest of the application on fire.
The project needed a name. Since it was about tearing down an old world to raise a new one, Norse mythology supplied the obvious one. So let me tell it the way the old skalds would — a saga of gods, realms and the bridge between them.
Here is the cosmology we will build:
- Midgard is the realm where the layers live — the map engine that actually draws things on screen.
- Asgard is the realm that rules all others — the application state, our Store.
- Odin is the powerful one: our business logic and UI, who commands from Asgard.
- Bifröst is the bridge between the realms, and Heimdall is its lone guardian — the one interface allowed to cross.
- Yggdrasil, the world tree that supports every realm, is Vue itself — the reactive framework whose sap carries a change in Asgard all the way down to Midgard.
And Ragnarök is what happens when the old world has to end so a new one can begin.
Odin walks in Midgard
In the old world, the gods walked freely into Midgard. Anywhere in the application, a component that wanted to show a shape simply reached straight into Leaflet and drew it. Convenient — and, in the long run, catastrophic.
When every god can walk into the realm of layers and reshape it at will, the land never stays coherent:
- Bugs scatter everywhere. A rendering glitch could originate from any of the dozens of places that talked to Leaflet directly. There was no single door to guard, so there was no single place to fix.
- The engine cannot be replaced. Leaflet calls were woven through business code, views and helpers. Replacing the engine would have meant rewriting a large part of the front-end — so in practice, we never could.
- Two philosophies fought each other. Our front-end is built with Vue 3, which is state-driven: you describe what should be shown and the framework reconciles the DOM. Leaflet is imperative: you tell it, step by step, to add this, remove that, move the other. Mixing both meant the map state and the application state constantly drifted apart, and we spent our time keeping them in sync by hand.
The lesson of the myth is the same as the lesson of the architecture: we do not want the gods to walk in Midgard anymore. Odin should rule from Asgard and let a single, trustworthy guardian carry his will across the bridge.
Yggdrasil: growing a new world
The new world is organized as a tree of well-separated realms, each with a single responsibility. Nothing reaches across a realm boundary except through the bridge.
| Realm | In the code | Responsibility |
|---|---|---|
| Asgard | The Store | Holds the application state (the truth) |
| Bifröst | The crossing | The channel state and events travel through |
| Heimdall | The Interface | Synchronizes the two — the only code aware of the engine |
| Midgard | The Map engine | Rendering and low-level interaction |
| Odin | The Views | Business logic & UI composition |
The single rule that makes the whole thing work is deceptively simple:
No one talks to the map engine except the Interface.
The application never imports Leaflet. It never holds a Leaflet object. It only ever reads from and writes to the Store. Whether Midgard is powered by Leaflet, MapLibre or something not yet written, Odin cannot tell the difference — and that is precisely the point.
At the top, the map component simply creates a Store, hands it to the application, and mounts one engine interface next to it:
public created(): void {
/* Asgard: the single source of truth, created once. */
this.innerStore = this.store ?? new MapStore({ /* ... */ });
/* Odin (the parent view) receives the store and drives the map
* exclusively through it. */
provide('store', this.innerStore);
this.$emit('store', this.innerStore);
}
public render() {
return (
<div class="map-container">
{/* Bifröst: the only bridge to Midgard. Swap this line and you
swap the whole rendering engine. */}
<LeafletInterface store={this.innerStore} on={/* ... */} />
{this.$slots.default?.()}
</div>
);
}
Notice how the engine appears exactly once, on a single line. Everything else
in the application talks to store, never to LeafletInterface.
Asgard rules all realms
The Store is Asgard: the one place that holds the truth. It is a plain, reactive Vue store, and it knows nothing about maps as pixels — only about maps as data. It is organized into a few clear sections:
content— what is rendered: the shapes, the markers, the layers.environment— how the map is currently shown: the viewport center, the zoom level, the active tools.tiles— the available background maps.lastActions— the last thing the user did (click, move), so the application can react to it.
Adding something to the map is therefore not a drawing call — it is a state change. Odin declares his intent, and lets the realm below take care of the pixels:
store.addLayer({
id: 'my-shape',
paneId: 'main',
shape: { type: 'Circle', coordinates: [5, 30], radius: 300 },
});
The Store records that a circle exists. It does not know, and does not care, how a circle is drawn. That knowledge belongs to Midgard, and the only way to get there is across the bridge.
Heimdall, guardian of the Bifröst
In the sagas, Heimdall is the watchman of the gods: he keeps his post at the end of the Bifröst, sees to the edges of the world and hears the grass grow, and lets nothing cross into Asgard unannounced — and it is his horn that will sound when Ragnarök comes. There is no better job description for a map interface.
Heimdall is the Interface: the single component in the entire codebase that is allowed to know which engine we use and to speak its language. The Bifröst is the crossing he guards, and he carries traffic in both directions — but each direction is a strict, one-way flow. Like Odin, Heimdall keeps two ravens to carry his messages across the bridge.
The bridge is made of three parts:
Hugin — the Store Watcher. In the myth, Hugin (“thought”) flies out to
carry Odin’s will into the world; here it carries the rendering flow, from
Asgard down to Midgard. It observes the reactive state and, whenever it
changes, tells the engine what to do. This is where the imperative world of
Leaflet is finally contained — behind a reactive watch:
/* Whenever the set of markers in the store changes, reconcile Leaflet. */
watch(() => store.content.markers, (markers, oldMarkers) => {
const diff = collectionDiff(markers, oldMarkers);
diff.removed.forEach((id) => removeMarkerFromLeaflet(id));
diff.added.forEach((id) => createMarker(markers.get(id)!));
/* ...and re-create the modified ones. */
}, { immediate: true, deep: true });
The application never called Leaflet — it just put a marker in the store, and the guardian did the rest.
Munin — the Map Listener. Munin (“memory”) is the raven that flies back to Odin with news of the world; here it carries the interaction flow, from Midgard back up to Asgard. When the user pans, zooms or clicks on the map, Leaflet emits an event. The listener catches it and translates it into a state change — committing it to Asgard’s memory — nothing more:
map.on('moveend', () => {
const center = map.getCenter();
store.center(center.lat, center.lng);
store.zoom(map.getZoom());
});
map.on('click', (evt) => {
store.setLastAction('click', { lat: evt.latlng.lat, lng: evt.latlng.lng });
});
The engine never mutates application state directly; it only reports what happened, and the Store decides what it means.
The Local Store is the guardian’s private ledger. Leaflet objects are not reactive and must not leak into Asgard, so the interface keeps its own non-reactive map between store identifiers and the concrete engine objects they correspond to. It is the glue that lets Hugin and Munin talk about “the same marker” without ever exposing a Leaflet handle to the rest of the world — and, just as importantly, it is what lets the guardian find and dispose of the previous Leaflet object when a layer changes, so that Midgard never leaks memory behind our backs.
Because these three parts — and only these three parts — know about the engine, the contract with the application is a clean, one-way loop: state flows down, interactions flow up, and Leaflet stays locked inside the bridge.
Two Midgards
The real proof of an abstraction is not that it is elegant on paper — it is that you can actually plug something else into it. So, during the hackathon, Midgard was built twice.
The first Midgard was Leaflet, wrapped behind the interface described above. It could display polygons and markers, follow the viewport, and report the user’s clicks and moves back to the Store. Enough to render a real screen from our product.
The second Midgard was MapLibre GL, a completely different, WebGL-based
engine. Building it meant writing a new interface — a new Store Watcher, a
new Map Listener, a new Local Store speaking MapLibre’s language — but touching
nothing in the Store and nothing in the application. The same state,
the same addLayer, the same clicks, rendered by an entirely different engine.
Visually, the two maps were almost indistinguishable — which was exactly the goal. If Odin cannot tell which realm is drawing his shapes, then the bridge has done its job.
Ragnarök: a world reborn
Two days were never going to be enough to move an entire product onto a new foundation. But they were enough to answer the only question that mattered: does the architecture hold? It did. State-driven maps, a single guardian for the engine, and a second engine plugged in behind the same contract — the old, tightly-coupled world had been torn down, and a cleaner one raised in its place. That upheaval is what gave the project its name.
From here, the roadmap writes itself. The same architecture opens the door to everything the old one made painful:
- Marker clustering, drill-down and viewport-based fetching for very large datasets.
- Restriction areas and richer geometry types.
- Finer layer control — explicit layer ordering, and interacting with any layer under the cursor instead of only the topmost one.
- Session persistence — bringing the user back to the exact map they left, viewport, zoom and all.
- Base map resilience, automatically switching background maps when a tile server goes silent.
- And, further out, swapping the engine itself — whether that means moving to a modern WebGL engine with vector tiles, or simply absorbing a breaking “Leaflet 2” major version. It need not be a rival library at all; either way it is one new guardian behind the same Asgard, and not a line of Odin’s code has to change.
None of it requires the gods to walk in Midgard ever again. They rule from Asgard, and Heimdall keeps the bridge.
Conclusion
Ragnarök was, in the end, less about maps than about boundaries. The feature shipped in two days — polygons and markers on two different engines — was almost beside the point. The real deliverable was a shape: a Store that owns the truth, an Interface that guards the crossing, and an engine that can be replaced without anyone else noticing.
The best proof that a boundary is well drawn is that you can put something new on the other side of it and nothing on your side has to change. That is exactly what happened here, and the new world has been growing ever since.
And yet, in the saga, Ragnarök is not a battle already won — it is the doom still to come, the one every warrior knows is coming and spends a lifetime preparing to meet. Ours is no different. We have not had to tear Leaflet out in production yet — and, if anything, we are eager for the day we finally do. But whenever it comes — a new engine, a breaking upgrade, a move to vector tiles — it will not catch us unprepared. The bridge is built, Heimdall stands watch, and Asgard is ready for the Ragnarök to come.
Disclaimer: Forged by human hands, this saga was sung together with an LLM skald and weighed by human eyes before the telling.
