Almost every slow web map is slow for one of two reasons: it asks the browser to draw more than the renderer can handle, or it ships more data over the network than the connection can deliver. Fixing web map performance means knowing which of those two budgets you have blown, then picking the rendering method and data delivery protocol that fit your dataset. This guide compares the three rendering paths available in a browser (SVG/DOM, Canvas 2D, and WebGL) and the main ways to get spatial data to the client (WMS, WMTS, XYZ tiles, WFS, vector tiles, and plain static files), with the trade-offs of each and clear guidance on when to reach for which.
Quick Answer
If you need a decision in thirty seconds, start here and read the details below.
| Situation | Rendering | Delivery |
|---|---|---|
| Under ~1,000 features, needs CSS styling and hover effects | SVG / DOM | Static GeoJSON |
| 1,000–50,000 features, standard interactive map | Canvas 2D | Static GeoJSON or vector tiles |
| 50,000+ features, animation, or 3D | WebGL | Vector tiles |
| Huge server-side dataset, display only | Server-rendered images | WMS or WMTS |
| Basemap that never changes between requests | Any | WMTS or XYZ tiles (cacheable) |
| Client must query, edit, or analyze geometry | Canvas or WebGL | WFS, OGC API Features, or a REST API |
| Continuous imagery or elevation rasters | Any | Cloud Optimized GeoTIFF or WMTS |
Where Web Map Performance Actually Goes
Before changing anything, it helps to separate the four costs that make up the time between a user's action and a finished frame. They fail in different ways and have different fixes.
- Transfer — bytes over the wire. Dominated by payload size, compression, and round trips. Symptom: a long blank period before anything appears.
- Parse and decode — turning those bytes into JavaScript objects or typed arrays. A 60 MB GeoJSON file can block the main thread for seconds in
JSON.parsealone, before a single pixel is drawn. Symptom: the page freezes after the download finishes. - Layout and draw — converting geometry to pixels. This is where the SVG/Canvas/WebGL choice matters most. Symptom: the first render is slow, and every pan or zoom stutters.
- Interaction — hit testing, tooltips, filtering, and restyling on every mouse move. Symptom: the map draws fine but feels sluggish when you move the cursor.
A useful rule: transfer and parse costs scale with data volume, while draw and interaction costs scale with feature count and vertex count. Simplifying geometry helps all four. Switching renderers only helps the last two.
Rendering Method 1: SVG and the DOM
The oldest approach gives every feature its own DOM node. Leaflet's default vector renderer works this way, as does D3's geographic output. Each polygon becomes an SVG <path> element that the browser lays out, paints, and composites like any other element on the page.
Pros: features are real DOM nodes, so you can style them with CSS, attach event listeners directly, animate them with CSS transitions, and inspect them in DevTools. Hit testing is free because the browser does it. Text rendering and accessibility are handled for you. Output is resolution independent, which matters if users print the map or export it to a vector graphic.
Cons: every node carries layout, style recalculation, and compositing overhead. Performance typically starts degrading in the low thousands of elements, and a pan that dirties the whole tree forces the browser to re-layout all of them. Complex polygons with tens of thousands of vertices make it worse, because path data must be re-parsed on each update.
Use it when you have a few hundred to a couple of thousand features, need rich per-feature interaction, or want the map to be part of the document (tooltips anchored to nodes, CSS-driven theming, animated transitions between states). A store locator, an election map of 50 states, or a small thematic overlay all fit comfortably.
Rendering Method 2: Canvas 2D
Canvas collapses the entire layer into one element. Features become paint commands on an immediate-mode surface: the browser holds no per-feature state, just the final bitmap. Leaflet switches to this mode with the preferCanvas: true map option, and OpenLayers uses Canvas as its default renderer.
Pros: the DOM stays flat, so there is no layout or style recalculation per feature. Drawing tens of thousands of simple geometries is realistic, and redraw cost depends on total vertex count rather than element count. Memory use is far lower than an equivalent SVG tree, and the API is straightforward to reason about.
Cons: you lose everything the DOM gave you. Hit testing must be implemented in JavaScript (usually a spatial index plus a point-in-polygon test, or an off-screen colour-keyed buffer). There are no CSS styles, no per-feature event listeners, and no accessibility tree. Changing the style of one feature means redrawing the layer, or at least the affected region. Output is raster, so it blurs when scaled unless you redraw at the new device pixel ratio.
Use it when you are in the thousands-to-tens-of-thousands range and still want client-side control over styling and interaction. This is the sweet spot for most data-heavy dashboards, and it is why the GeoDataTools map viewer initialises Leaflet with preferCanvas enabled: imported files routinely contain far more features than an SVG tree handles gracefully.
Rendering Method 3: WebGL
WebGL hands geometry to the GPU as vertex buffers and lets shaders rasterise it. MapLibre GL JS, Mapbox GL JS, deck.gl, and OpenLayers' WebGL layers all take this path. Geometry is uploaded once, then re-rendered every frame at essentially no CPU cost, which is what makes continuous zoom, rotation, tilt, and per-frame animation feel smooth.
Pros: the highest feature ceiling by a wide margin, comfortably into the hundreds of thousands of features with the right data pipeline. Zoom and rotation are free because the GPU re-projects existing buffers. Data-driven styling, extrusions, 3D terrain, heatmaps, and per-frame animation all become practical. Label placement and collision detection run on the GPU in the mature libraries.
Cons: the highest complexity. Geometry must be triangulated (tessellated) before upload, which costs CPU time up front and can produce artefacts on self-intersecting polygons. Buffer uploads consume GPU memory, and updating a single feature often means rebuilding a buffer. Debugging shaders is nothing like debugging DOM. Context loss on mobile has to be handled explicitly, and older or software-rendered devices may fall back to something much slower than Canvas.
Use it when feature counts are large enough that Canvas drops frames, when you need 3D or continuous animation, or when you want the smooth fractional zoom users expect from a modern basemap. For a broader comparison of the libraries that expose these renderers, see OpenLayers vs Leaflet.
Rendering Method 4: Let the Server Draw It
The fourth option is not to render on the client at all. A server such as GeoServer or MapServer reads from the database, applies styling, and returns a finished PNG or JPEG. The browser only decodes an image, which is a fixed cost regardless of whether that image represents 10 features or 10 million.
Pros: client cost is constant and tiny. The browser never sees the underlying data, which matters when licensing restricts redistribution. Cartographic quality is high because the server can use a full styling language and label engine. Legacy and enterprise data sources are often already published this way.
Cons: no client-side interaction with geometry. Restyling, filtering, and hover effects all require a server round trip, so the map feels heavy. Images are raster and fixed to the requested resolution and projection. Server CPU becomes the bottleneck under load, and every distinct view is a cache miss unless requests are tiled.
Use it when the dataset is too large to send, the client only needs to look at it, or the data already lives behind an OGC service. The rest of this guide covers how those services differ.
Rendering Comparison Table
| Aspect | SVG / DOM | Canvas 2D | WebGL | Server-rendered |
|---|---|---|---|---|
| Practical feature ceiling | ~1,000–5,000 | ~10,000–100,000 | 100,000+ | Unlimited (server-side) |
| Hit testing | Free (browser) | Manual | Manual or GPU picking | Server request |
| Per-feature CSS/events | Yes | No | No | No |
| Restyle cost | Cheap, per element | Redraw layer | Rebuild buffer or shader uniform | New request |
| Animation | CSS transitions | Manual redraw loop | Per-frame, GPU | Not practical |
| 3D / tilt | No | No | Yes | No |
| Accessibility | Real DOM nodes | None built in | None built in | Alt text only |
| Implementation effort | Low | Medium | High | Low client, high ops |
Delivery Method 1: WMS
The OGC Web Map Service returns a rendered image for an arbitrary bounding box. The client sends a GetMap request with a bbox, size, CRS, and layer list, and the server responds with a PNG or JPEG. GetCapabilities advertises the available layers, and GetFeatureInfo returns attributes for a clicked pixel.
Pros: arbitrary extents and sizes, server-side styling via SLD, any supported projection, and near-universal support in GIS servers and clients. Layers can be composited server-side into a single request.
Cons: because the bbox is arbitrary, responses are effectively uncacheable — pan by one pixel and you get a cache miss. Rendering happens on demand, so server CPU scales with traffic. Latency is on the critical path for every view change, and the client has no geometry to work with.
Use it when you need flexible extents, on-the-fly reprojection, or dynamic server-side filtering (for example, a CQL filter applied per user). For anything a user pans and zooms around repeatedly, put a tile cache in front of it. Our guide to working with WMS services covers the request parameters in detail.
Delivery Method 2: WMTS and XYZ Tiles
Web Map Tile Service is the OGC standard for pre-rendered tiles on a fixed grid. Instead of an arbitrary bbox, the client requests a specific tile by matrix set, zoom level, row, and column, so every request maps to a stable URL. The informal XYZ (or "slippy map") convention used by most basemap providers is the same idea with a simpler URL template.
Pros: stable URLs make tiles trivially cacheable at every layer — browser, CDN, and reverse proxy. Tiles can be rendered once and served as static files, which turns an expensive rendering service into cheap object storage. Response times are predictable and parallel requests fill the viewport quickly.
Cons: the tile grid is fixed, so you get one projection (usually EPSG:3857) and discrete zoom levels. Styling is baked in at render time, so changing symbology means re-rendering the cache. Seeding a full pyramid for a large area is expensive in storage and time, and frequently updated data means constant invalidation.
Use it when the data is relatively static and many users view the same areas: basemaps, imagery, and reference layers. This is the default choice for any layer that does not need to change per user.
Delivery Method 3: WFS and Feature APIs
The OGC Web Feature Service returns actual geometry and attributes rather than pictures, in GML or, on most modern servers, GeoJSON. OGC API - Features is its modern REST successor, and a hand-rolled REST endpoint backed by PostGIS falls in the same category.
Pros: the client owns the data. You can restyle without a round trip, run client-side filters and measurements, export to another format, and support editing through WFS-T. Attribute and spatial filters can be pushed to the server so only the relevant subset is transferred.
Cons: payloads are large and untiled by default. A careless request can return the entire layer, and GML in particular is verbose. Everything then has to be parsed on the main thread. Without a bbox filter, a paging strategy, and a maximum feature count, WFS is the fastest way to freeze a browser tab.
Use it when the client genuinely needs geometry: editing tools, measurement, spatial selection, or client-side analysis. Always constrain requests by bounding box and attribute filter, and request GeoJSON over GML when the server offers it. See WFS vs WMS explained for a fuller treatment of the split, and what is WFS for the operations themselves.
Delivery Method 4: Vector Tiles
Vector tiles combine the two ideas above: geometry is cut into a tile pyramid, simplified per zoom level, and encoded in a compact binary format, usually the Mapbox Vector Tile specification built on protocol buffers. The client receives geometry, not pixels, but only for the tiles in view and only at the detail that zoom level warrants.
Pros: small payloads with cacheable tile URLs, plus client-side styling and interaction. Restyling is instant because the geometry is already there. Detail scales automatically with zoom, so a world view never ships street-level vertices. Overzooming lets the client keep rendering while new tiles load. Formats like PMTiles package an entire pyramid into a single file served with HTTP range requests, which removes the need for a tile server entirely.
Cons: generating tiles is a real build step, and updates mean re-tiling. Geometry is quantised to a tile grid and clipped at tile boundaries, so features can be split across tiles and coordinates are not exact — a problem for precise measurement. Attribute payloads are per-tile, so joining data across tiles requires care. Practical use assumes a renderer that speaks the format, which in practice means Canvas or WebGL.
Use it when you have a large dataset that users explore interactively and you want both fast loading and client-side styling. It is the default architecture for modern basemaps and most large thematic layers.
Delivery Method 5: Static Files
Sometimes the fastest option is a file on a CDN. A GeoJSON file, a TopoJSON file, or a FlatGeobuf binary served from object storage has no server to scale, no query to plan, and no cache to invalidate.
Pros: zero infrastructure, perfect CDN caching, and trivial deployment. TopoJSON removes duplicate shared borders and typically lands 60–80% smaller than the equivalent GeoJSON. FlatGeobuf adds a packed spatial index and streams, so a client can fetch only the features intersecting a bbox using range requests. Cloud Optimized GeoTIFF does the equivalent for rasters with internal tiling and overviews.
Cons: no server-side filtering, so the client either downloads everything or the format has to support range requests. Updates mean redeploying the file. Plain GeoJSON in particular is verbose, and parse time on the main thread grows quickly past a few megabytes.
Use it when the dataset is bounded, changes rarely, and is small enough to ship — administrative boundaries, a set of routes, a snapshot for a report. Compress it, simplify it, and prefer a binary or topology-aware format once it passes a few megabytes.
Delivery Comparison Table
| Method | Returns | Cacheable | Client-side styling | Best for |
|---|---|---|---|---|
| WMS | Rendered image, any bbox | Poorly | No | Dynamic server-styled layers, any projection |
| WMTS / XYZ | Rendered image, fixed grid | Excellent | No | Basemaps, imagery, static reference layers |
| WFS / OGC API Features | Geometry and attributes | Depends on query | Yes | Editing, querying, client-side analysis |
| Vector tiles (MVT/PMTiles) | Tiled binary geometry | Excellent | Yes | Large interactive datasets, modern basemaps |
| Static GeoJSON / TopoJSON | Whole file | Excellent | Yes | Small, stable datasets |
| FlatGeobuf / COG | Range-requested subsets | Excellent | Yes | Large files on object storage without a server |
Optimizations That Help Regardless of Stack
Renderer and protocol choices set the ceiling. These techniques decide how close you get to it.
- Simplify geometry to the zoom level. Coastlines digitised at survey accuracy carry vertices no screen can resolve. Douglas-Peucker or Visvalingam simplification often removes 80–95% of vertices with no visible change at typical web zooms. Run it once, offline, with the GeoJSON simplifier rather than on every page load.
- Trim coordinate precision. Six decimal places of longitude is roughly 10 cm at the equator. Storing fifteen wastes bytes on every coordinate in the file, and those bytes compress poorly because the digits are effectively noise.
- Drop unused attributes. Properties you never display still cost transfer and parse time. Strip them server-side or at build time.
- Filter by bounding box. Never request features outside the viewport when the source supports a spatial filter. This is the single highest-impact change for WFS-backed maps.
- Cluster or aggregate dense points. Ten thousand overlapping markers convey no more information than a few hundred clusters and cost far more to draw. Libraries like Supercluster do the aggregation in milliseconds.
- Parse off the main thread. Move large
JSON.parseor binary decoding into a Web Worker so the UI keeps responding while data loads. - Enable compression. GeoJSON is highly repetitive text; gzip or Brotli routinely cuts it by 70–90%. Verify the header is actually present on your tile and API responses.
- Set cache headers deliberately. Immutable tiles deserve long
max-agevalues and a CDN in front. This is what makes the tiled protocols worth their constraints. - Debounce interaction work. Hit tests and tooltip updates on every
mousemoveare a common source of jank that has nothing to do with how the layer is drawn. - Match the projection. Reprojecting on the fly in the client costs CPU on every frame. Store data in the CRS your map uses when you can — see EPSG:4326 vs EPSG:3857.
How to Measure Instead of Guess
Every recommendation above depends on your data. Confirm the bottleneck before you rebuild anything.
- Network panel — check payload sizes, whether responses are compressed, and whether tiles are being served from cache. A 40 MB uncompressed GeoJSON tells you where to start.
- Performance profile — record a pan and a zoom. Long tasks in
JSON.parsepoint at data volume; long tasks in the renderer point at feature and vertex counts; scripting spikes onmousemovepoint at interaction handlers. - Frame rate during interaction — the number that users actually feel. If pans stutter while the layer is static, the renderer is the constraint, not the network.
- Interaction to Next Paint — the Core Web Vitals responsiveness metric that replaced First Input Delay in 2024. Heavy synchronous map work shows up here directly, and it affects search visibility.
- Feature and vertex counts — count them before optimising. Ten thousand points and ten thousand complex polygons are entirely different problems.
Putting It Together: Three Worked Scenarios
A national boundaries overlay on a dashboard. A few hundred polygons with heavy vertex counts. Simplify offline, convert to TopoJSON, serve as a static file from a CDN, render with Canvas. No server needed and the whole layer arrives in one cached request.
A parcel viewer covering a metropolitan area. Millions of polygons, needs identify-on-click. Serve vector tiles for display, keep a WFS or feature API for the click query so exact geometry is fetched only for the selected parcel. Render with WebGL if the tile styling is complex, Canvas if it is simple.
A legacy imagery service inside an internal tool. Data is already published as WMS and cannot be re-tiled. Put a tile cache in front of it so repeated views become WMTS-style cached requests, and accept that interaction stays server-side. See the web mapping architecture guide for where a cache fits in the stack.
FAQ
What is the fastest way to render a lot of features on a web map?
WebGL is the fastest renderer for large feature counts, because geometry is uploaded to the GPU once and redrawn each frame at almost no CPU cost. But the delivery method matters just as much: pair WebGL with vector tiles so only the features in the current viewport, at the current zoom, are ever loaded.
Is Canvas or SVG better for map rendering?
SVG is better below roughly a thousand features because you get free hit testing, CSS styling, and per-feature events. Canvas is better above that because it removes per-feature DOM overhead, at the cost of implementing hit testing and restyling yourself. In Leaflet you switch to Canvas with the preferCanvas map option.
When should I use vector tiles instead of GeoJSON?
Use vector tiles when the dataset is too large to send in one request, when users pan and zoom across a wide area, or when you want per-zoom detail without maintaining several files. Plain GeoJSON is fine for bounded datasets up to a few megabytes that change rarely.
What is the difference between WMS and WMTS for performance?
WMS renders an image for an arbitrary bounding box, so almost every request is a cache miss and the server renders on demand. WMTS requests tiles from a fixed grid, so URLs are stable and can be cached by the browser and a CDN. For repeated viewing of the same layer, WMTS is dramatically faster and cheaper to run.
Why does my map freeze after the data finishes downloading?
That is a parse or draw bottleneck, not a network one. A large GeoJSON file blocks the main thread inside JSON.parse before rendering begins. Move parsing into a Web Worker, simplify the geometry, reduce coordinate precision, or switch to a tiled or binary format.
Does simplifying geometry lose accuracy?
Yes, by design, but usually below what the screen can display. Simplification tolerance should be tied to the zoom levels you serve, and analytical work should keep the full-precision source. Topology-preserving simplification avoids gaps and overlaps between adjacent polygons.
Prepare Your Data for a Faster Map
Most web map performance work starts with the data, not the renderer. Reducing vertex count, trimming precision, and choosing a compact format usually delivers a bigger improvement than swapping libraries, and it costs an afternoon rather than a rewrite. You can do the first pass in the browser: simplify GeoJSON to cut vertices, convert to TopoJSON to remove duplicated borders, and load the result into the map viewer to check that the shapes still look right at the zoom levels you care about. For guidance on structuring the data itself, see GeoJSON best practices.
Cut vertices before you blame the renderer
Simplify geometry in your browser and see how much file size and draw cost you can remove.
Open GeoJSON SimplifierOr open the full app →