tessl install tessl/npm-react-leaflet@5.0.3React components for Leaflet maps
Tile layer components display raster map tiles from tile servers. They form the base layer of most maps, providing the background cartography.
Displays tiled map layers loaded from a URL template.
/**
* Component for displaying raster tile layers
* @param props - Tile layer properties
*/
const TileLayer: FunctionComponent<TileLayerProps>;
interface TileLayerProps extends TileLayerOptions, LayerProps {
/** URL template for tiles (e.g., "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png") */
url: string;
}Usage Example:
import { MapContainer, TileLayer } from "react-leaflet";
function Map() {
return (
<MapContainer center={[51.505, -0.09]} zoom={13}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
);
}Common Tile Providers:
// OpenStreetMap
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
/>
// Stamen Terrain
<TileLayer
url="https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg"
attribution='Map tiles by <a href="http://stamen.com">Stamen Design</a>'
/>
// CartoDB Positron
<TileLayer
url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/attributions">CARTO</a>'
/>
// Mapbox (requires API key)
<TileLayer
url="https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}"
attribution='© <a href="https://www.mapbox.com/">Mapbox</a>'
id="mapbox/streets-v11"
accessToken="YOUR_MAPBOX_ACCESS_TOKEN"
/>
// OpenTopoMap
<TileLayer
url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
attribution='Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, SRTM | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a>'
maxZoom={17}
/>Displays tiles from WMS (Web Map Service) servers.
/**
* Component for displaying WMS tile layers
* @param props - WMS tile layer properties
*/
const WMSTileLayer: FunctionComponent<WMSTileLayerProps>;
interface WMSTileLayerProps extends WMSOptions, LayerProps {
/** WMS service base URL */
url: string;
/** WMS request parameters */
params?: WMSParams;
}Usage Example:
import { MapContainer, WMSTileLayer } from "react-leaflet";
function Map() {
return (
<MapContainer center={[51.505, -0.09]} zoom={13}>
<WMSTileLayer
url="https://example.com/wms"
params={{
layers: "my_layer",
format: "image/png",
transparent: true,
}}
attribution="WMS Service"
/>
</MapContainer>
);
}Advanced WMS Example:
<WMSTileLayer
url="https://geo.weather.gc.ca/geomet"
params={{
layers: "RADAR_1KM_RRAI",
format: "image/png",
transparent: true,
version: "1.3.0",
time: "2024-01-01T12:00:00Z",
}}
opacity={0.7}
zIndex={1000}
/>// From Leaflet
interface TileLayerOptions extends GridLayerOptions {
/** Minimum zoom level for tiles */
minZoom?: number;
/** Maximum zoom level for tiles */
maxZoom?: number;
/** Subdomains of the tile service (for load balancing) */
subdomains?: string | string[];
/** Error tile URL shown when tile fails to load */
errorTileUrl?: string;
/** Zoom offset for tile coordinates */
zoomOffset?: number;
/** If true, inverse Y axis numbering (TMS) */
tms?: boolean;
/** If true, zoom bounds check */
zoomReverse?: boolean;
/** Tile size multiplier for retina displays */
detectRetina?: boolean;
/** Custom headers for tile requests */
headers?: Record<string, string>;
/** Crossorigin attribute for tiles */
crossOrigin?: boolean | string;
/** Referrer policy for tile requests */
referrerPolicy?: string;
}
interface GridLayerOptions extends LayerOptions {
/** Width and height of tiles in pixels */
tileSize?: number | Point;
/** Opacity of the tiles */
opacity?: number;
/** Whether to update map while tiles load */
updateWhenIdle?: boolean;
/** Whether to update map while zooming */
updateWhenZooming?: boolean;
/** Interval to throttle updateWhenZooming */
updateInterval?: number;
/** z-index for the layer */
zIndex?: number;
/** Geographic bounds of the layer */
bounds?: LatLngBoundsExpression;
/** Keep this many rows/cols of tiles loaded */
keepBuffer?: number;
/** Class name for tile container */
className?: string;
}// From Leaflet
interface WMSOptions extends TileLayerOptions {
/** Comma-separated list of WMS layers */
layers?: string;
/** Comma-separated list of WMS styles */
styles?: string;
/** Image format (e.g., 'image/png') */
format?: string;
/** Whether to request transparent tiles */
transparent?: boolean;
/** WMS version string */
version?: string;
/** Coordinate Reference System */
crs?: CRS;
/** Whether to use uppercase parameter names */
uppercase?: boolean;
}
interface WMSParams {
/** WMS layers parameter */
layers: string;
/** WMS styles parameter */
styles?: string;
/** Image format */
format?: string;
/** Whether to request transparent tiles */
transparent?: boolean;
/** WMS version */
version?: string;
/** Additional WMS parameters */
[key: string]: string | number | boolean;
}interface LayerProps extends EventedProps, LayerOptions {
/** Pane where the layer should be rendered */
pane?: string;
/** Attribution text for the layer */
attribution?: string;
}
interface LayerOptions {
pane?: string;
attribution?: string;
}
interface EventedProps {
/** Map of event types to handlers */
eventHandlers?: LeafletEventHandlerFnMap;
}URL Template Variables: Tile URLs support these template variables:
{z} - Zoom level{x} - Tile X coordinate{y} - Tile Y coordinate{s} - Subdomain (from subdomains option){r} - Retina marker (@2x)Attribution: Always include proper attribution for tile providers as required by their terms of service.
Retina Tiles: Use detectRetina: true to automatically load higher resolution tiles on retina displays:
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
detectRetina={true}
/>CORS: Some tile servers require CORS configuration. Use the crossOrigin prop if needed:
<TileLayer
url="https://example.com/{z}/{x}/{y}.png"
crossOrigin={true}
/>Multiple Tile Layers: You can stack multiple tile layers. Use opacity for blending:
<TileLayer url="base-layer-url" />
<TileLayer url="overlay-layer-url" opacity={0.5} />Z-Index Control: Use zIndex to control layer stacking order:
<TileLayer url="background-url" zIndex={1} />
<TileLayer url="foreground-url" zIndex={2} />WMS Parameters: WMS layers require specific parameters. Common ones include:
params={{
layers: "layer1,layer2",
format: "image/png",
transparent: true,
version: "1.1.1",
}}Tile Loading Events: Use event handlers to track tile loading:
<TileLayer
url="..."
eventHandlers={{
loading: () => console.log("Tiles loading"),
load: () => console.log("Tiles loaded"),
tileerror: (error) => console.error("Tile error", error),
}}
/>Custom Panes: Render tiles in specific panes using the pane prop:
<TileLayer url="..." pane="tilePane" />Bounds Restriction: Limit tile loading to specific geographic bounds:
<TileLayer
url="..."
bounds={[[40.7, -74.0], [40.8, -73.9]]}
/>Tile Size: Customize tile size for non-standard tile servers:
<TileLayer
url="..."
tileSize={512}
zoomOffset={-1} // Adjust zoom offset when using different tile sizes
/>TMS Coordinate System: Some tile servers use TMS (Tile Map Service) coordinate system:
<TileLayer
url="..."
tms={true} // Inverts Y axis numbering
/>function MapWithLayerSwitch() {
const [layer, setLayer] = useState("osm");
return (
<>
<select value={layer} onChange={(e) => setLayer(e.target.value)}>
<option value="osm">OpenStreetMap</option>
<option value="topo">Topographic</option>
</select>
<MapContainer center={[51.505, -0.09]} zoom={13}>
{layer === "osm" && (
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
)}
{layer === "topo" && (
<TileLayer url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png" />
)}
</MapContainer>
</>
);
}<TileLayer
url="https://{s}.mytiles.com/{z}/{x}/{y}.png"
subdomains={["a", "b", "c", "d"]}
attribution="Custom tiles"
/><WMSTileLayer
url="https://wms.example.com/service"
params={{
layers: "layer1,layer2,layer3",
format: "image/png",
transparent: true,
version: "1.3.0",
}}
/>function TileLayerWithLoading() {
const [loading, setLoading] = useState(true);
return (
<>
{loading && <div className="loading-spinner">Loading tiles...</div>}
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
eventHandlers={{
loading: () => setLoading(true),
load: () => setLoading(false),
}}
/>
</>
);
}function MapWithOverlay() {
const [showOverlay, setShowOverlay] = useState(true);
return (
<MapContainer center={[51.505, -0.09]} zoom={13}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
{showOverlay && (
<TileLayer
url="https://tiles.example.com/overlay/{z}/{x}/{y}.png"
opacity={0.5}
zIndex={1000}
/>
)}
</MapContainer>
);
}Problem: Tiles don't appear or show error icons.
Possible Causes and Solutions:
Incorrect URL:
CORS Issues:
<TileLayer
url="..."
crossOrigin={true}
/>Attribution Missing:
Rate Limiting:
Zoom Level Out of Range:
<TileLayer
url="..."
minZoom={0}
maxZoom={18}
/>Problem: Tiles take a long time to load.
Solutions:
Use Subdomains for Parallel Loading:
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
subdomains={['a', 'b', 'c']}
/>Reduce Tile Buffer:
<TileLayer
url="..."
keepBuffer={2} // Default is 2, reduce for less memory usage
/>Optimize Update Behavior:
<TileLayer
url="..."
updateWhenIdle={true} // Only update tiles when map is idle
updateWhenZooming={false} // Don't update during zoom
/>Use CDN or Faster Tile Server:
Problem: Tiles look blurry on high-DPI displays.
Solution:
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
detectRetina={true}
/>Or use explicit retina tiles:
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}@2x.png"
/>Problem: Visible gaps between tiles.
Solutions:
Ensure Correct Tile Size:
<TileLayer
url="..."
tileSize={256} // Standard tile size
/>Check Zoom Offset:
<TileLayer
url="..."
tileSize={512}
zoomOffset={-1} // Adjust for non-standard tile sizes
/>Disable Tile Fading:
<MapContainer fadeAnimation={false}>
<TileLayer url="..." />
</MapContainer>Problem: WMS layer doesn't appear.
Solutions:
Verify WMS Parameters:
<WMSTileLayer
url="https://example.com/wms"
params={{
layers: "layer_name", // Must match server layer name exactly
format: "image/png",
transparent: true,
version: "1.3.0", // Check server version
}}
/>Check CRS Compatibility:
<WMSTileLayer
url="..."
params={{ ... }}
crs={L.CRS.EPSG4326} // Match server CRS
/>Test WMS URL Directly:
Problem: Browser crashes or slows down with many tiles.
Solutions:
Reduce Keep Buffer:
<TileLayer
url="..."
keepBuffer={1} // Reduce from default of 2
/>Limit Zoom Levels:
<TileLayer
url="..."
minZoom={5}
maxZoom={15} // Prevent loading too many tiles at high zoom
/>Use Update When Idle:
<TileLayer
url="..."
updateWhenIdle={true}
/>Problem: Attribution text doesn't appear.
Solutions:
Enable Attribution Control:
<MapContainer attributionControl={true}>
<TileLayer
url="..."
attribution='© <a href="...">Provider</a>'
/>
</MapContainer>Check Attribution Control Position:
<AttributionControl position="bottomright" />Verify Attribution String:
Problem: Custom tile server tiles not loading.
Solutions:
Verify Tile Naming Convention:
/{z}/{x}/{y}.png/{z}/{x}/{y}.png with tms: trueCheck Server CORS Headers:
Access-Control-Allow-Origin: * headerTest Tile URLs:
// Log tile URLs for debugging
<TileLayer
url="..."
eventHandlers={{
tileloadstart: (e) => console.log('Loading tile:', e.url),
tileerror: (e) => console.error('Tile error:', e.url),
}}
/>updateWhenIdle: true for better performanceimport { render } from '@testing-library/react';
import { MapContainer, TileLayer } from 'react-leaflet';
test('renders tile layer', () => {
const { container } = render(
<MapContainer center={[51.505, -0.09]} zoom={13}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap'
/>
</MapContainer>
);
expect(container).toBeInTheDocument();
});test('renders WMS tile layer with params', () => {
render(
<MapContainer center={[51.505, -0.09]} zoom={13}>
<WMSTileLayer
url="https://example.com/wms"
params={{
layers: 'test_layer',
format: 'image/png',
transparent: true,
}}
/>
</MapContainer>
);
// Add assertions
});import type { TileLayerOptions } from 'leaflet';
interface TileLayerConfig {
url: string;
attribution: string;
options?: TileLayerOptions;
}
const tileConfigs: TileLayerConfig[] = [
{
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: '© OpenStreetMap',
options: {
maxZoom: 19,
minZoom: 0,
},
},
];
// Usage
{tileConfigs.map((config, idx) => (
<TileLayer
key={idx}
url={config.url}
attribution={config.attribution}
{...config.options}
/>
))}Always include proper attribution for map tiles to comply with tile provider terms and help users understand data sources:
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
// Attribution is important for legal compliance and transparency
/>function WeatherMap() {
const [timestamp, setTimestamp] = useState(new Date().toISOString());
useEffect(() => {
const interval = setInterval(() => {
setTimestamp(new Date().toISOString());
}, 300000); // Update every 5 minutes
return () => clearInterval(interval);
}, []);
return (
<MapContainer center={[51.505, -0.09]} zoom={13}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<WMSTileLayer
url="https://weather.example.com/wms"
params={{
layers: "radar",
time: timestamp,
format: "image/png",
transparent: true,
}}
key={timestamp} // Force re-render when timestamp changes
/>
</MapContainer>
);
}function RobustTileLayer({ url, fallbackUrl }) {
const [currentUrl, setCurrentUrl] = useState(url);
const [errorCount, setErrorCount] = useState(0);
const handleTileError = useCallback(() => {
setErrorCount(prev => prev + 1);
if (errorCount > 10 && fallbackUrl) {
console.warn('Switching to fallback tile server');
setCurrentUrl(fallbackUrl);
}
}, [errorCount, fallbackUrl]);
return (
<TileLayer
url={currentUrl}
eventHandlers={{
tileerror: handleTileError,
}}
/>
);
}function ZoomDependentTiles() {
const [zoom, setZoom] = useState(13);
return (
<MapContainer center={[51.505, -0.09]} zoom={zoom}>
<MapEvents onZoomChange={setZoom} />
{zoom < 10 ? (
<TileLayer url="https://low-detail-tiles.example.com/{z}/{x}/{y}.png" />
) : (
<TileLayer url="https://high-detail-tiles.example.com/{z}/{x}/{y}.png" />
)}
</MapContainer>
);
}
function MapEvents({ onZoomChange }) {
const map = useMapEvents({
zoomend: () => {
onZoomChange(map.getZoom());
},
});
return null;
}