Add vector tiles to a React app: troubleshooting guide

A practical troubleshooting guide for rendering vector tiles in a React application with MapLibre GL JS and the official react-maplibre integration. It focuses on diagnosing blank maps, incorrect source-layer names, tile URL and projection problems, CSS sizing, worker bundling, React lifecycle issues, and production differences.

Vector tiles let a browser request only the geographic data needed for the current viewport and zoom level, then render that data with a client-side style. In a React application, the most common failures are not caused by React itself. They usually come from a mismatch between the tile service and the MapLibre style configuration: an incorrect TileJSON URL, a missing source-layer, unsupported tile coordinates, an invisible layer, or a map container with no usable height. This guide uses MapLibre GL JS with @vis.gl/react-maplibre and is designed to help you isolate those problems systematically.

How EDK fits

For: Frontend developers and GIS developers building React applications that display MapLibre or Mapbox Vector Tiles, especially developers diagnosing blank maps, missing layers, failed tile requests, WebGL errors, or inconsistent behavior between development and production.

Vector-tile troubleshooting is a small but important part of making the physical world findable and computable in software. A map renderer does not understand a road, building, stream, service request, or public asset merely because a tile loaded successfully: the application must correctly connect geographic coordinates, tiled source data, source-layer names, feature properties, zoom ranges, and visual rules. That separation is useful when thinking about a broader world model such as E2. Rendering is the presentation layer; finding, measuring, ranking, filtering, comparing, and computing over physical features require reliable geographic identities, geometry, attributes, and spatial relationships underneath. A correctly configured vector-tile client can therefore serve as the visual inspection surface for datasets such as roads, waterways, buildings, infrastructure, or public-service observations, while computation remains a separate concern.

  • •Use a vector-tile map as a visual validation surface for geographic features such as residential roads, streams, parks, buildings, hydrants, or service-request locations. The map can help confirm whether a feature appears in the expected region, zoom range, source layer, and geometry type before deeper spatial analysis.
  • •Pair rendered vector-tile layers with geographic computation workflows: for example, inspect a map of roads or public assets, then use a world-model system to filter features by region, calculate proximity to infrastructure, compare neighborhoods, aggregate observations, or detect changes. The renderer displays results; it does not itself establish that those computations exist.
  • •Use source-layer and feature-property inspection to make heterogeneous location data understandable. A single map can expose whether a dataset contains line, polygon, or point features and whether properties needed for styling or interaction—such as class, category, identifier, or status—are actually present.
Renderer
MapLibre GL JS, a WebGL-based browser library for interactive vector-tile maps. (maplibre.org)
React integration
@vis.gl/react-maplibre, a React component suite for MapLibre GL JS. (visgl.github.io)
Vector source formats
MapLibre Tile format or Mapbox Vector Tile format. (maplibre.org)
Required vector-layer field
source-layer must identify the internal layer in a vector tile. (maplibre.org)
Default tile scheme
XYZ unless the source specifies TMS. (maplibre.org)

The EDK outcome

Before you start

An existing React application with a working development and production build.

Node.js and a package manager such as npm, pnpm, or yarn.

A browser with WebGL enabled.

A permitted MapLibre-compatible vector-tile endpoint or TileJSON document.

The provider's documented attribution, authentication, licensing, and usage requirements.

The expected internal source-layer name and the feature properties needed for styling or interaction.

Learning objectives

Distinguish a vector source from a rendered style layer.

Identify why a valid tile request can still produce no visible features.

Debug CSS sizing, network, CORS, authentication, worker, source-layer, zoom, filter, and style-expression failures.

Configure a minimal React vector-tile map before adding application state and interactions.

Recognize which problems belong to the tile service, MapLibre style, React wrapper, or build system.

Verification

The map container has nonzero width and height.

The style document loads without a validation error.

The vector source metadata or tile request succeeds.

Tile responses contain supported vector-tile data rather than HTML or an API error.

The source ID in the layer matches the registered source ID.

The source-layer exactly matches an internal layer in the tile.

The layer geometry type matches the data geometry.

The current zoom falls within the source and layer ranges.

The layer is visible and has no filter that excludes every feature.

The production build loads its worker and all map assets successfully.

Attribution and provider usage requirements are visible and satisfied.

Troubleshooting

Blank rectangle with no map: inspect the container's computed height, then check whether the style URL is reachable and whether the browser reports a WebGL or JavaScript error.

Background map works, custom data does not: inspect vector tile requests and verify source-layer. The source ID is an application-side name; source-layer comes from inside the tile.

404 tile requests: verify the z/x/y order, URL encoding, path prefix, extension, and whether the provider expects XYZ or TMS.

401 or 403 responses: verify the token, request headers, referrer restrictions, allowed origins, and whether credentials are required for tile requests.

CORS errors: configure the tile and style server to permit the deployed web origin, or use an approved server-side proxy consistent with the provider's terms.

Features appear only at unexpected zooms: inspect minzoom and maxzoom on both source and layer, plus any zoom-based visibility expression.

Features are the wrong color or absent after adding a filter: inspect the property name and value type. Numeric and string values are not interchangeable in expressions.

Lines or polygons are clipped at tile boundaries: vector tiles are tiled and may clip or simplify geometry. Reduce oversized styling effects or adjust tile-generation buffers when you control tile creation. (maplibre.org)

Hover or selection state does not work: establish stable feature IDs or configure promoteId, then set feature state programmatically. (maplibre.org)

Development works but production fails: test the production worker, inspect CSP and asset paths, compare environment variables, and verify that the deployed origin can reach every required map resource.

React state appears stale: avoid mixing controlled props with direct native mutations. Use the wrapper's callbacks and props as the primary state path, or isolate imperative operations behind a ref.

Next steps

Read the companion guide for the initial React and MapLibre implementation.

Use the production guide for caching, deployment, worker hosting, attribution, authentication, and operational checks.

Add feature inspection only after the base layer renders; use stable feature IDs when building hover or selection state.

Treat rendered tiles as a visualization and inspection layer, then perform spatial filtering, measurement, aggregation, proximity, routing, or change analysis in the appropriate geographic computation system.

Validate representative places and zoom levels, including dense urban areas, rural regions, tile boundaries, and locations with sparse or missing coverage.

What you need before debugging

Use a React application with a working JavaScript build, a browser with WebGL enabled, and a vector-tile endpoint that you are authorized to access. The endpoint should expose either a TileJSON URL through the source url property or a tiles template containing {z}, {x}, and {y}. MapLibre supports MapLibre Tile or Mapbox Vector Tile data for vector sources. (maplibre.org)

You also need the internal source-layer name. This is not necessarily the same as the public dataset name, URL path, or source identifier. For example, a source may be registered under roads, while the tile's internal layer is named transport or road_lines. Vector layers must reference that internal name through source-layer. (maplibre.org)

  • •Node.js and an existing React project
  • •@vis.gl/react-maplibre
  • •maplibre-gl
  • •A vector-tile URL or TileJSON document
  • •The expected source-layer name and feature properties
  • •A test location and zoom level where data should be present
  • •Attribution requirements supplied by the tile provider

Minimal known-good configuration

Start with the smallest possible map. Avoid adding application state, dynamic filters, hover effects, custom controls, or multiple sources until one source and one layer render correctly. The wrapper's Map component accepts a MapLibre style URL or a style object, and its style prop controls the map container's CSS dimensions. (visgl.github.io)

This example uses a complete style object so the relationship between source, source-layer, and rendered layer is explicit. Replace the tile URL and source-layer value with values from your provider:

Diagnose the failure in the browser

Open browser developer tools before changing code. The Console identifies style validation, WebGL, worker, and JavaScript errors. The Network panel identifies failed style, TileJSON, sprite, glyph, and tile requests. Filter requests by pbf, mvt, json, or the tile host.

If the map container is present but empty, inspect its computed width and height. A map with height: 100% can collapse when none of its parent elements has an explicit height. Give the map a temporary fixed height to distinguish a CSS problem from a data problem.

Fix the most common source and layer mismatches

A vector source describes tile delivery. A style layer describes rendering. Both must be present, and the layer must point to the source by its registered ID. For a vector source, source-layer is mandatory; omitting it or using the wrong value commonly produces a valid-looking map with no visible features. (maplibre.org)

Check these values together:

  • •sources.my-data.type must be vector.
  • •sources.my-data.url must point to valid TileJSON, or sources.my-data.tiles must contain a valid tile template.
  • •layers[].source must equal the source ID, such as my-data.
  • •layers[].source-layer must equal the internal vector-tile layer name exactly, including capitalization.
  • •layers[].type must fit the geometry you intend to draw: line for linear features, fill for polygons, and circle or symbol for point features.
  • •The layer's minzoom and maxzoom must include the current map zoom.
  • •The layer's layout.visibility must not be none.
  • •Filters must match actual feature properties and value types.

React lifecycle, workers, and production differences

react-maplibre is a reactive wrapper around MapLibre GL JS. Its documentation describes both uncontrolled maps, which manage camera state after initial construction, and controlled maps, which receive camera state through props and report changes through callbacks. Choose one approach deliberately. Mixing a controlled camera with imperative updates can cause the application state and the native map instance to disagree. (visgl.github.io)

Prefer changing mapStyle, camera props, and event handlers through React props. Use a ref or useMap when you genuinely need an imperative MapLibre operation, such as querying rendered features or calling flyTo. The wrapper notes that direct native calls can cause the underlying map state to diverge from React props. (visgl.github.io)

A repeatable troubleshooting workflow

The fastest debugging method is to remove variables one at a time. First prove that the map can render a known-good style. Then prove that the custom source loads. Then prove that the source-layer exists. Finally add filters, expressions, interactions, and application state.

For a source that loads but remains invisible, temporarily replace the layer's filter with no filter, use a high-contrast paint color, remove zoom limits, and set a large line width or circle radius. If features appear, the problem is in styling or filtering rather than tile delivery.

  • •Test the container with a fixed height.
  • •Load a known-good base style.
  • •Check style and tile requests in Network tools.
  • •Inspect console errors and MapLibre error events.
  • •Verify source type, URL, scheme, and encoding.
  • •Verify the exact source-layer name.
  • •Remove filters and zoom constraints.
  • •Use a high-contrast paint property.
  • •Test the production build locally.
  • •Reintroduce application state and interactions one feature at a time.

Workflow

01

1. Install the renderer and React wrapper

Add the packages to the React project. Keep the MapLibre CSS import in the application bundle so controls and map-related layout styles are available.

npm install maplibre-gl @vis.gl/react-maplibre
02

2. Create a map with explicit dimensions

Start with a fixed height. This eliminates the most common CSS failure before you investigate geographic data.

import {Map} from '@vis.gl/react-maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';

export default function App() {
  return (
    <Map
      initialViewState={{longitude: -122.4, latitude: 37.8, zoom: 10}}
      style={{width: '100%', height: 500}}
      mapStyle="https://demotiles.maplibre.org/style.json"
    />
  );
}
03

3. Register the vector source and a matching layer

Use either a TileJSON URL or a direct tile template. Replace demo values with the provider's documented values. The source-layer must be the internal vector-tile layer name, not merely the source ID or dataset title.

const style = {
  version: 8,
  sources: {
    roads: {
      type: 'vector',
      url: 'https://example.test/tiles/roads.json'
      // Or: tiles: ['https://example.test/roads/{z}/{x}/{y}.pbf']
    }
  },
  layers: [
    {
      id: 'roads-line',
      type: 'line',
      source: 'roads',
      'source-layer': 'road_lines',
      paint: {
        'line-color': '#2563eb',
        'line-width': 2
      }
    }
  ]
};

<Map
  initialViewState={{longitude: -122.4, latitude: 37.8, zoom: 12}}
  style={{width: '100%', height: 500}}
  mapStyle={style}
/>
04

4. Inspect the first failing request

In developer tools, identify whether the failure is the style document, TileJSON, an individual tile, a sprite, a glyph, or the worker. Record the HTTP status, response body, Content-Type, request URL, and whether the request is blocked by CORS or CSP.

// Optional runtime diagnostics
<Map
  {...props}
  onError={event => console.error('MapLibre error', event.error)}
  onSourceData={event => console.debug('Source event', event.sourceId, event.sourceDataType, event.isSourceLoaded)}
/>
05

5. Verify source-layer, geometry, and properties

If requests succeed but features are invisible, remove filters and zoom constraints. Confirm that a line layer is being used for line geometry, a fill layer for polygons, and a circle or symbol layer for points. If styling depends on a property, confirm the property exists in the tile and has the expected type.

// Diagnostic layer: deliberately simple and conspicuous
{
  id: 'diagnostic-layer',
  type: 'line',
  source: 'roads',
  'source-layer': 'road_lines',
  paint: {'line-color': '#ff00ff', 'line-width': 5}
}
06

6. Reintroduce React state and production configuration

Once the static map renders, add controlled camera state, filters, interactions, and custom controls one at a time. Then run the production build locally and test the deployed-style asset paths, worker loading, CSP, CORS, credentials, and attribution.

npm run build
npm run preview

Questions teams ask

Why does the map background load while my vector layer is blank?

The style can load successfully while the custom vector layer fails independently. Check the tile requests, then verify that the layer's source ID matches the registered source and that source-layer exactly matches the internal layer name in the tile. Also remove filters, minzoom, maxzoom, and visibility settings temporarily. Vector layers require source-layer; a wrong value commonly produces no visible features even when the tile request succeeds. (maplibre.org)

Should I use a TileJSON URL or a tiles URL template?

Use TileJSON when the provider publishes it because it can describe tiles, bounds, zoom limits, attribution, and related source metadata. Use tiles when you have a direct template such as https://example.test/tiles/{z}/{x}/{y}.pbf and know the service's scheme and limits. Both are supported vector-source configurations, but the provider's documentation should determine which is correct. (maplibre.org)

Why does the map work in development but fail after deployment?

Compare the production build's worker output, CSP, CORS behavior, asset paths, and environment variables with development. In Vite, MapLibre's documented worker import pattern matters because an incorrectly emitted worker can fail to load its sibling module. Also verify that the deployed origin is allowed to request styles, tiles, sprites, and glyphs. (maplibre.org)

Should I call MapLibre methods directly from a React component?

Use React props for state that the wrapper exposes, especially map style and controlled camera state. Use a ref or useMap for operations that are inherently imperative, such as flyTo or lower-level inspection. Direct native calls can make the underlying map differ from the React props, so keep one clear source of truth. (visgl.github.io)

Why do features disappear at some zoom levels?

Check the source and layer minzoom/maxzoom values, the provider's available zoom range, and any zoom-based expressions. At high zooms, MapLibre may overzoom tiles from the source's maximum zoom rather than receive new higher-resolution data. At low zooms, a provider may not publish the feature class or may simplify it away. (maplibre.org)

Keep exploring

Sources

More in Guides