Leaflet в production-среде требует не только корректной инициализации карты, но и системной настройки окружения, сборки, загрузки ресурсов, работы с тайлами, кеширования и оптимизации рендеринга. Поведение, которое в разработке кажется приемлемым, в production быстро приводит к деградации производительности, утечкам памяти и росту сетевого трафика.
В production-окружении не используется прямое подключение через unpkg или cdn без контроля версий. Основной подход — сборка через bundler (Vite, Webpack, Rollup).
npm install leaflet
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
Ключевой момент — явное подключение CSS. Отсутствие стилей приводит к некорректному отображению тайлов и контролов.
Одна из типичных проблем production-сборки — потеря путей к изображениям маркеров.
import iconUrl from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';
import L from 'leaflet';
L.Icon.Default.mergeOptions({
iconUrl,
shadowUrl: iconShadow
});
Без этой настройки в production окружении маркеры часто отображаются как пустые блоки.
Production-конфигурация предполагает строгий контроль жизненного цикла карты.
const map = L.map('map', {
center: [51.505, -0.09],
zoom: 13,
zoomControl: false,
attributionControl: false
});
Контролы часто отключаются для кастомной UI-обвязки, что снижает DOM-нагрузку.
При уничтожении компонента обязательно освобождение ресурсов:
map.remove();
Игнорирование удаления карты приводит к накоплению слушателей событий и росту памяти.
Основная нагрузка в Leaflet связана с загрузкой тайлов. Production требует контроля кэша, ограничения зума и настройки субдоменов.
L.tileLayer('https://{s}.tile.provider.com/{z}/{x}/{y}.png', {
maxZoom: 18,
minZoom: 3,
tileSize: 256,
updateWhenIdle: true,
keepBuffer: 2,
crossOrigin: true
}).addTo(map);
Production-система всегда использует внешние или self-hosted tile server’ы. Публичные бесплатные провайдеры не подходят для нагрузки.
Типовая схема:
Пример:
const baseLayer = L.tileLayer('https://tiles.mycdn.com/base/{z}/{x}/{y}.png', {
maxZoom: 19,
subdomains: ['a', 'b', 'c'],
detectRetina: true
});
В production важно минимизировать повторные запросы. Используются:
Service Worker слой:
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/tiles/')) {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
return caches.open('tiles-cache').then((cache) => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
}
});
При большом количестве объектов производительность падает линейно. Решение — кластеризация.
import 'leaflet.markercluster';
const markers = L.markerClusterGroup({
chunkedLoading: true,
spiderfyOnMaxZoom: true,
showCoverageOnHover: false
});
markers.addLayer(L.marker([51.5, -0.09]));
map.addLayer(markers);
Частая ошибка production — постоянные вызовы invalidateSize и setView без необходимости.
Правильный подход:
requestAnimationFrame(() => {
map.invalidateSize();
});
Использование debounce:
function debounce(fn, delay) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), delay);
};
}
Работа с GeoJSON должна учитывать размер данных.
L.geoJSON(data, {
style: {
weight: 2,
opacity: 0.8
},
onEachFeature: (feature, layer) => {
layer.bindPopup(feature.properties.name);
}
}).addTo(map);
Для production важно:
Загрузка данных по границам карты:
map.on('moveend', () => {
const bounds = map.getBounds();
fetch(`/api/geo?bbox=${bounds.toBBoxString()}`)
.then(r => r.json())
.then(data => {
L.geoJSON(data).addTo(map);
});
});
Обязательная оптимизация — предотвращение повторных запросов при мелких сдвигах.
Leaflet использует DOM-слои для маркеров и SVG/Canvas для векторных объектов.
Production-настройка:
L.map('map', {
preferCanvas: true
});
Canvas снижает нагрузку при большом количестве объектов.
В production часто включается Content Security Policy, что ломает работу inline-стилей и внешних скриптов тайловых провайдеров.
Пример безопасной политики:
Content-Security-Policy:
default-src 'self';
img-src 'self' https://tiles.mycdn.com;
connect-src 'self' https://api.myapp.com;
Production-система должна учитывать недоступность тайлов:
const layer = L.tileLayer(url, {
errorTileUrl: '/images/tile-error.png'
});
Дополнительно:
При анимациях и потоковых данных необходимо ограничивать обновления маркеров:
let lastUpdate = 0;
function update(data) {
const now = Date.now();
if (now - lastUpdate < 100) return;
lastUpdate = now;
// обновление слоя
}
Типовая структура:
Разделение критично для масштабируемости и тестирования.
Leaflet не поддерживает server-side rendering напрямую. Любая инициализация карты должна происходить только на клиенте.
if (typeof window !== 'undefined') {
const map = L.map('map');
}
Инициализация вне этого условия приводит к ошибкам сборки.
Production требует tree-shaking и исключения неиспользуемых модулей:
const HeatLayer = await import('leaflet.heat');
В production важно отслеживать:
Инструменты:
Критические моменты:
Игнорирование этих операций приводит к утечкам при SPA-навигации.