Application rendering and data hosting strategies
When building web mapping applications, you might need to consider how the application is rendered for search engine optimization (SEO) and where the spatial data is hosted. These decisions can significantly impact performance, discoverability, scalability, and user experience.
Application rendering strategies
As opposed to client-side rendering (CSR) like many existing React applications on the web today, static site generators (SSG) such as Astro, Nuxt, and Next.js offer advantages like SEO, simplified page routing and server-side rendering (SSR). However, mapping libraries are often problematic when integrated with SSR environments due to their reliance on the browser's window object, which is unavailable during SSR. You might find yourself needing to write workarounds when trying to use web mapping functionalities within SSG-based applications.
The SSR example below shows a workaround that had to be done in order to get Leaflet to work in a SSG-based application. If the CSR Vue component is rendered on the server, it will throw an error because window is not defined.
<script setup>
import { onMounted } from 'vue';
let Leaflet;
onMounted(async () => {
// Import dynamically to avoid window not defined error in SSR
Leaflet = await import('leaflet');
// Check if window is defined
if (typeof window !== 'undefined') {
const map = Leaflet.map('map').setView([48.43, -123.36], 14);
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([48.43, -123.36])
.addTo(map)
.bindPopup('Find our cafe here!')
.openPopup();
}
});
</script><script setup>
import { onMounted } from 'vue';
// Direct import in CSR
import * as Leaflet from 'leaflet';
onMounted(() => {
const map = Leaflet.map('map').setView([48.43, -123.36], 14);
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([48.43, -123.36])
.addTo(map)
.bindPopup('Find our cafe here!')
.openPopup();
});
</script>Luckily, most SSGs provide built-in solutions to handle such scenarios, allowing you to specify that certain components should only be rendered on the client-side. Check out how VitePress <ClientOnly>, Astro client directives, and React "use client" offer simpler workarounds.
TIP
This site was built with VitePress which is a Vite and Vue powered SSG. You can check out all the code for this site here.
If you're interested in SEO, you should read up Vercel's research of how Google handles JavaScript through the indexing process based on different rendering strategies.
Afterwards, try passing your website through Google Search Console's URL Inspection Tool to see how Googlebot views your web mapping application for SEO.
Client-side/server-side data
When a user visits a web mapping application, the application can load spatial data on the client-side directly in the user's browser. For example, with Leaflet, you can load a CSV or GeoJSON file directly into the map. This is ideal for small datasets or offline use in disconnected/air gapped environments. CSR, as covered in rendering strategies above, often uses client-side data.
import * as L from 'leaflet';
const map = L.map("map").setView([48.43, -123.36], 14);
fetch('points.geojson')
.then(response => response.json())
.then(data => {
L.geoJSON(data).addTo(map);
});Server-side data is more scalable for enterprise/large company needs because it is hosted externally and accessible via APIs or services on a remote server or cloud service.
You can load web maps and other geospatial data that you make with Map Viewer directly from ArcGIS Online, or connect to your own hosted GIS services using the ArcGIS Maps SDK for JavaScript. Map Viewer is a feature of ArcGIS Online and ArcGIS Enterprise that aims to simplify the creation and sharing of web maps.
<body>
<arcgis-map id="map">
<arcgis-zoom slot="top-left"></arcgis-zoom>
</arcgis-map>
<script type="module" src="main.js"></script>
</body>import Map from "@arcgis/core/Map.js";
import Layer from "@arcgis/core/layers/Layer.js";
const viewElement = document.getElementById("map");
viewElement.addEventListener("arcgisViewReadyChange", async (event) => {
const layer = await Layer.fromPortalItem({
portalItem: {
id: "11e173a4e2f040ae80d39e44ee29467a",
},
});
viewElement.map = new Map({
basemap: "topo-vector",
layers: [layer],
});
viewElement.center = [-73.97, 40.77];
viewElement.zoom = 10;
});Self-hosting requires setting up a GIS server (such as ArcGIS Enterprise or GeoServer), caching, maintenance, publishing services, understanding relational databases, and managing authentication and access. Loading from a paid service like ArcGIS Online is relatively simple in comparison.
However, loading data from a server isn't limited to the ArcGIS Maps SDK for JavaScript. You can do something similar with OpenLayers and GeoServer.
import Vector from 'ol/source/Vector.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import { bbox } from 'ol/loadingstrategy.js';
// Load vector data from a remote GeoServer using WFS and GeoJSON
const vectorSource = new Vector({
format: new GeoJSON(),
loader: function(extent, resolution, projection, success, failure) {
const proj = projection.getCode();
const url = 'https://ahocevar.com/geoserver/wfs?service=WFS&' +
'version=1.1.0&request=GetFeature&typename=osm:water_areas&' +
'outputFormat=application/json&srsname=' + proj + '&' +
'bbox=' + extent.join(',') + ',' + proj;
fetch(url)
.then(response => response.json())
.then(data => {
const features = vectorSource.getFormat().readFeatures(data);
vectorSource.addFeatures(features);
success(features);
})
.catch(() => {
vectorSource.removeLoadedExtent(extent);
failure();
});
},
strategy: bbox,
});Test your knowledge
For a simple web map to display a business location, which might be sufficient?
Next steps
WARNING
Exercise for how to host your own web mapping application via GitHub Pages is a work in progress.