Add vector tiles to a React app
A practical guide to rendering vector tiles in React with MapLibre GL JS and the official react-maplibre integration. It covers project setup, style documents, TileJSON, vector sources, source layers, zoom-dependent styling, feature interaction, validation, and production tradeoffs.
Vector tiles are a browser delivery format for geographic features. Instead of downloading an entire regional dataset, a map requests tiles for the current viewport and zoom level, then renders the features locally. In a React application, React manages application state and component structure while MapLibre GL JS handles WebGL rendering, tile loading, camera movement, and map interaction. The official react-maplibre integration supplies React components for the MapLibre renderer, including Map, Source, and Layer. MapLibre’s visual appearance is controlled by a style document that defines sources, layers, drawing order, and presentation rules. (visgl.github.io)
How EDK fits
For: Frontend developers and geospatial developers who already have a React application and need to display a vector-tile service, add a custom geographic overlay, or diagnose why a vector source is not appearing on the map.
Vector tiles are a presentation and delivery layer for geographic information. They divide physical-world features into tiles, expose selected geometries and attributes at different zoom levels, and let a browser render roads, buildings, waterways, boundaries, infrastructure, or other mapped objects on demand. That makes this workflow relevant to E2’s broader world-model perspective, but the relationship is limited and editorial: adding vector tiles to React does not itself create a world model, provide a dataset, or perform geographic computation. The engineering pattern becomes useful when a React interface needs to turn geographic search or computation into an interactive visual result. For example, a map can render a ranked set of roads, buildings, public assets, service requests, waterways, or environmental features after another system has selected, filtered, measured, or aggregated them. The vector-tile layer is therefore the browser-facing visualization step between geographic computation and a user’s understanding of the physical world.
- •Render the output of a geographic search or ranking workflow as tiled roads, buildings, waterways, public assets, or other physical features without sending an entire regional dataset to the browser at once.
- •Visualize computed geographic results—such as features inside a boundary, objects within a distance threshold, or changes between observations—as a styled vector layer in a React application.
- •Use source-layer styling and zoom-dependent visibility to present the same geographic subject at multiple levels, from a regional overview to detailed street- or building-scale inspection.
The EDK outcome
Before you start
Basic React and TypeScript or JavaScript knowledge.
An existing React application with a working build pipeline.
Node.js LTS and npm, pnpm, or yarn.
A MapLibre-compatible style URL or style JSON.
A vector-tile service documented with TileJSON or an XYZ/TMS tile URL template.
The tileset’s internal source-layer name and relevant property names.
Permission to use the map style, vector tiles, fonts, sprites, and underlying geographic data.
Learning objectives
Install MapLibre GL JS and the React integration.
Distinguish between vector sources, internal source layers, and rendered style layers.
Add a TileJSON URL or tile URL template to a React map.
Style vector features with geometry-specific layers, feature properties, filters, and zoom expressions.
Handle common failures involving source-layer names, CORS, zoom ranges, styles, and tile endpoints.
Understand why vector-tile rendering complements, rather than replaces, geographic search and computation.
Verification
The map wrapper has a nonzero width and height.
The browser network panel shows successful style and tile requests.
The tile response uses a supported vector-tile format.
The source-layer exactly matches the internal layer name in the tileset.
The camera is within the tile service’s documented geographic bounds.
The current zoom is within the source’s available zoom range.
The MapLibre CSS file is loaded.
The style has valid glyph and sprite URLs when labels or icons are used.
The rendered geometry type matches the chosen layer type.
Attribution and data-use requirements are visible and accurate.
Troubleshooting
Blank map: confirm that the map container has nonzero height, the style URL is reachable, and the browser supports WebGL.
Basemap appears but the overlay does not: check source-layer, source ID, layer ID uniqueness, geometry type, camera bounds, zoom range, and layer visibility.
404 tile responses: inspect the generated z/x/y path and confirm whether the service expects XYZ or TMS addressing.
403 or CORS errors: verify credentials, allowed origins, HTTPS, and the tile provider’s browser-access policy.
Labels or icons are missing: inspect glyph and sprite URLs, then verify that the referenced font stacks and icon names exist in the style.
Features appear only at some zooms: inspect minzoom, maxzoom, tile-generation rules, and whether the provider generalizes or drops attributes at lower zooms.
Incorrect colors or filters: verify the property name and value types in the actual tile payload; numeric and string values do not behave identically in expressions.
Visual gaps appear at tile edges: inspect the tile-generation buffer and the sizes of lines, circles, symbols, or offsets. Large rendered values can expose insufficient tile buffers. (maplibre.org)
React state behaves unexpectedly after direct map calls: prefer declarative wrapper props when possible, because imperative native-map changes can cause the underlying map state to diverge from React props. (visgl.github.io)
The map works locally but fails in production: compare the deployed origin, content-security policy, worker configuration, HTTPS status, asset paths, and tile-provider allowlist with local development settings.
Next steps
Create a production configuration for style URLs, tile endpoints, attribution, and credentials.
Add loading and error states based on map and source events.
Document source-layer names, feature properties, geometry types, and zoom availability for every tileset used by the application.
Add a feature detail panel that connects map selection to the underlying geographic record or computation service.
Measure tile sizes, request counts, cache hit rates, parsing time, and interaction latency at representative regional and city-scale views.
Separate visualization tiles from analytical APIs so users can inspect results without treating display-oriented tiles as the authoritative dataset.
Link the rendered map to a world-model workflow only when the upstream system genuinely provides geographic search, comparison, filtering, measurement, ranking, or other computation about physical features.
What you are building
The example creates a React map backed by MapLibre GL JS through the official react-maplibre package. It loads a base style, adds a separate vector-tile source, and draws one internal source layer with a line layer. This separation is central to MapLibre’s model: the source describes the geographic data, while the style layer describes how selected source data should be drawn. One vector-tile source can contain several internal layers—for example, roads, water, land use, or places—and each rendered layer selects one of them with source-layer. (maplibre.org)
You need a valid MapLibre style URL or inline style object, a vector-tile endpoint or TileJSON document, and the exact internal source-layer name. A complete style may already define a basemap, glyphs, sprites, sources, and layers. If you add raw vector tiles to another style, you must supply the source and visual layers yourself, and you must confirm that the service returns MapLibre Tile or Mapbox Vector Tile data supported by the source configuration. (maplibre.org)
- •An existing React application with a working build pipeline.
- •Node.js LTS and a browser with WebGL support.
- •A MapLibre-compatible style URL or style JSON.
- •A vector-tile service documented with TileJSON or an XYZ/TMS URL template.
- •The exact internal source-layer name and the feature properties you intend to style.
- •Permission to use the style, vector tiles, fonts, sprites, and underlying geographic data.
Install the React and map packages
For a new application, install the dedicated react-maplibre package together with maplibre-gl. The official getting-started documentation shows this package combination and requires the MapLibre stylesheet. The stylesheet is not cosmetic only: the map container and built-in controls depend on it for expected layout and presentation. (visgl.github.io)
The broader react-map-gl project also documents a MapLibre entry point, but avoid mixing import styles casually within one project. Pick the package and import path that match the documentation and version installed in your application. The examples below use @vis.gl/react-maplibre because it is the dedicated current getting-started path in the official documentation. (visgl.github.io)
- •Install the dependencies:
- •Keep the MapLibre CSS import in the application bundle:
- •If your project uses server-side rendering, initialize the map only where browser APIs are available and follow your framework’s client-component or dynamic-import conventions.
Create the map container and load a style
A MapLibre map needs a container with a nonzero width and height. The style document controls what data is drawn, the order in which layers are drawn, and how those layers are styled. The camera’s initial longitude, latitude, and zoom determine the first requested tiles, so begin in an area covered by your vector service rather than assuming the service is global. (maplibre.org)
The public demonstration style below is suitable for trying the component structure, but it should not be treated as a production dependency. A real application should select a style and tile service whose attribution, rate limits, licensing, uptime, and data coverage are appropriate for the product.
- •Use an explicit height on the map wrapper.
- •Use a style URL or inline style object that MapLibre can fetch and parse.
- •Start the camera inside the known bounds of the vector-tile service.
- •Keep style, tile, glyph, and sprite origins consistent with the provider’s browser-access policy.
Add a vector source and rendered layer
A vector source can be configured with a TileJSON URL through url or with one or more tile URL templates through tiles. The TileJSON route is often easier when the provider publishes metadata such as bounds, minimum and maximum zoom, attribution, and tile templates. A direct template is useful when your service already exposes a predictable {z}/{x}/{y} path. MapLibre documents xyz as the default scheme and tms as an alternative for services whose y-coordinate direction follows the TMS convention. (maplibre.org)
The Layer component then selects the internal source-layer and supplies a geometry-specific rendering type. Use line for linear features, fill for polygons, and circle or symbol for point features. If the source loads but the overlay is invisible, an incorrect source-layer value is one of the first things to check. A source can be valid while the requested internal layer name is not. (maplibre.org)
- •Use url when the provider supplies a TileJSON endpoint.
- •Use tiles when the provider supplies an XYZ or TMS URL template.
- •Set source-layer to the exact internal layer name, including capitalization.
- •Set minzoom and maxzoom only when they reflect the service’s documented availability.
- •Include attribution when required by the provider or data license.
Style by feature properties and zoom
Vector-tile rendering becomes useful when the visual treatment responds to map scale and feature attributes. MapLibre expressions can read a property with get, choose branches with match or case, and interpolate values over zoom. For example, a road layer can use a class property to differentiate motorways, primary roads, and secondary roads while increasing line width as the user zooms in.
The property must actually be present in the tile payload at the zoom level being viewed. A field in the source database is not automatically available in every generated tile. Tile-generation rules may omit, simplify, or generalize data at lower zooms. The browser can style what the tiles contain, but it cannot recover an attribute or feature that the tiling pipeline removed. This is an important distinction when a map is used to present computed geographic results: display tiles should not silently be treated as the complete analytical dataset.
- •```tsx const roadsLayer = { id: 'road-classification', type: 'line' as const, source: 'custom-vector-source', 'source-layer': 'roads', paint: { 'line-color': [ 'match', ['get', 'class'], 'motorway', '#dc2626', 'primary', '#f59e0b', 'secondary', '#eab308', '#64748b' ], 'line-width': [ 'interpolate', ['linear'], ['zoom'], 6, 0.5, 12, 2, 16, 6 ] } }; ```
- •Keep property names and value types documented as part of the tileset contract.
- •Use layer-level minzoom and maxzoom when a feature class should appear only at particular scales.
- •Use filters when the map should display only a subset of features.
Add interaction while keeping React state coherent
For simple selection, use the map component’s event props and interactiveLayerIds. The event can expose the rendered feature and its properties, allowing the application to update a sidebar, detail panel, or selected-feature state. Keep selected IDs, search terms, filters, and other product state in React, then express the visual consequence through layer filters, expressions, or component props.
For advanced operations, the React integration exposes a ref and methods for accessing the underlying map. Use those methods deliberately. The official API documentation notes that some imperative changes can make the native map state disagree with React props; when a behavior can be represented declaratively, prefer the prop-based approach. (visgl.github.io)
- •Use interactiveLayerIds so pointer events target the layers that matter.
- •Store selected feature identifiers in React rather than in an unrelated global map mutation.
- •Inspect feature properties before assuming a field such as name or class exists.
- •Use the map ref for operations that genuinely require MapLibre’s imperative API.
- •Avoid recreating the map when unrelated application state changes.
Validate the delivery chain before production
Vector tiles can reduce browser transfer and support smooth multi-zoom rendering, but they introduce several contracts that must be tested independently. The style must load; the tile requests must resolve; the source must use the correct addressing scheme; the source-layer must exist; the current camera must fall inside the service’s coverage; and the rendered layer must match the geometry type. Fonts, sprites, attribution, and licensing are separate concerns from the tile payload.
Test the same map at a regional overview, a city-scale view, and a detailed street or building view. A layer that appears at one scale may be intentionally generalized or unavailable at another. Also inspect tile-edge behavior for thick lines, large circles, symbols, or offsets. MapLibre notes that features render within their originating tile, so insufficient tile buffers can produce visible artifacts at boundaries. (maplibre.org)
- •Use HTTPS for the application, style, and tile endpoints.
- •Inspect browser network requests for 404, 403, CORS, and malformed responses.
- •Confirm attribution and data-use requirements are visible and accurate.
- •Check glyph and sprite URLs when labels or icons are used.
- •Measure request counts, tile sizes, cache behavior, and interaction latency.
- •Keep display-oriented tiles separate from authoritative analytical or editing data.
Workflow
1. Install the dependencies
Install the dedicated React integration and MapLibre GL JS. The official getting-started path uses Node.js LTS, React, @vis.gl/react-maplibre, and maplibre-gl.
npm install @vis.gl/react-maplibre maplibre-gl2. Import the MapLibre components and stylesheet
Import Map, Source, and Layer from the React integration. Load the MapLibre stylesheet in the application bundle so the map container and built-in controls receive their required styles.
import { Map, Source, Layer } from '@vis.gl/react-maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';3. Create a map with a known starting view
Give the wrapper an explicit height and choose a starting camera inside the style and tile service’s coverage. The style URL defines the initial visual system and its available basemap sources.
export default function MapView() {
return (
<div style={{ width: '100%', height: 600 }}>
<Map
initialViewState={{
longitude: -122.4,
latitude: 37.8,
zoom: 10
}}
mapStyle="https://demotiles.maplibre.org/style.json"
/>
</div>
);
}4. Add the vector source
Use url when the provider gives you a TileJSON endpoint. Use tiles when the provider gives you a URL template. Do not guess the source-layer name from the endpoint; obtain it from the tileset metadata or provider documentation.
<Source
id="custom-vector-source"
type="vector"
url="https://example.com/tiles/tiles.json"
>
{/* rendered layers go here */}
</Source>5. Add a rendered style layer
Select a layer type matching the feature geometry and set source-layer to the exact internal vector-tile layer name. The source supplies data; the Layer component supplies its visual representation.
<Layer
id="custom-roads"
type="line"
source="custom-vector-source"
source-layer="roads"
paint={{
'line-color': '#2563eb',
'line-width': 2
}}
/>6. Add property- and zoom-driven styling
Use MapLibre expressions to read feature properties and interpolate visual values over zoom. Confirm that the selected properties exist in the tile payload at the zoom levels where the layer is visible.
const roadsLayer = {
id: 'road-classification',
type: 'line' as const,
source: 'custom-vector-source',
'source-layer': 'roads',
paint: {
'line-color': [
'match', ['get', 'class'],
'motorway', '#dc2626',
'primary', '#f59e0b',
'#64748b'
],
'line-width': [
'interpolate', ['linear'], ['zoom'],
6, 0.5,
16, 6
]
}
};7. Add interaction and application state
Use interactiveLayerIds and event handlers to identify rendered features under a click or pointer event. Keep selected IDs, filters, search terms, and other product state in React, then express visual changes with props, filters, or expressions.
<Map
interactiveLayerIds={['custom-roads']}
onClick={(event) => {
const feature = event.features?.[0];
console.log(feature?.properties);
}}
>
<Source id="custom-vector-source" type="vector" url="https://example.com/tiles/tiles.json">
<Layer {...roadsLayer} />
</Source>
</Map>8. Validate the complete delivery chain
Test the style URL, tile requests, source-layer name, zoom range, geometry type, property names, CORS headers, attribution, glyphs, sprites, and licensing. Build and preview the application before testing production-like hosting.
npm run build
npm run previewQuestions teams ask
Why does my vector source load but nothing appear?
The most common cause is an incorrect source-layer value. A vector-tile source can contain multiple internal layers, and each MapLibre layer must reference the exact internal name. Also check that the camera is within the tile coverage, the current zoom is within the source’s available range, the geometry type matches the chosen layer type, the layer is not hidden by a filter or visibility rule, and the style is not drawing another layer above it. (maplibre.org)
Do I need a complete style URL to display vector tiles?
You need a valid MapLibre style document, either as a URL or an inline object. A complete style may provide the basemap, glyphs, sprites, sources, and existing layers. If you build a minimal style yourself, define the source and rendered layers you need, and provide glyph or sprite URLs when labels or icons depend on them. A style document controls what data is drawn, drawing order, and visual presentation. (maplibre.org)
Can I use a raw .pbf URL instead of TileJSON?
Usually you provide a URL template containing {z}, {x}, and {y}, rather than one fixed .pbf file. MapLibre accepts a tiles array for vector sources, while TileJSON provides a structured description of the tileset and related metadata. Confirm whether the provider uses XYZ or TMS addressing and whether its responses are in a supported MapLibre Tile or Mapbox Vector Tile format. (maplibre.org)
Should I use GeoJSON instead of vector tiles?
Use GeoJSON when the dataset is small enough for direct feature delivery or when complete feature access is more important than tiled multi-zoom rendering. Use vector tiles when the geographic coverage or feature count makes viewport-based delivery and server-side generalization more useful. The right choice depends on feature count, update frequency, styling needs, interaction behavior, and whether the browser needs the complete dataset.
Does adding vector tiles let my React app perform spatial analysis?
No. It adds a rendering and interaction path. Spatial search, measurement, routing, aggregation, change detection, and other computations require appropriate data and computation services or client-side libraries. A map can visualize those results, but the tile layer alone should not be treated as an analytical system.
Keep exploring
build a web map with maplibre
A broader MapLibre guide for initializing a web map, working with styles, camera state, and interaction before adding a custom vector source.
add vector tiles to a react app production guide
A complementary production guide focused on deployment, caching, observability, attribution, credentials, and operational readiness.
add vector tiles to a react app troubleshooting guide
A focused troubleshooting reference for CORS failures, incorrect source-layer names, tile errors, style problems, and rendering artifacts.
Sources
MapLibre GL JS introduction and quickstart
Official documentation for installing MapLibre GL JS, importing its CSS, initializing a map, and understanding its WebGL and style-document model.
Open sourceMapLibre Style Specification: Sources
Authoritative reference for vector sources, TileJSON, tile URL templates, source-layer requirements, bounds, zoom ranges, schemes, attribution, and supported tile formats.
Open sourceMapLibre Style Specification: Layers
Defines layer types, source references, source-layer usage, filters, zoom visibility, and the relationship between data sources and rendered map layers.
Open sourceMapLibre Style Specification introduction
Explains the role of a MapLibre style document in determining what data is drawn, layer ordering, and visual appearance.
Open sourcereact-maplibre getting started
Official React integration setup showing package installation, Map usage, initial view state, MapLibre styles, and the required stylesheet.
Open sourcereact-map-gl adding custom data
Official vis.gl guidance for using Map and Source/Layer components to add geographic data and native map layers from React.
Open sourcereact-maplibre Map API
Documents event handling, map refs, native-map access, and the distinction between declarative React props and imperative map methods.
Open source