Конфигурация для production

Leaflet в production-среде требует не только корректной инициализации карты, но и системной настройки окружения, сборки, загрузки ресурсов, работы с тайлами, кеширования и оптимизации рендеринга. Поведение, которое в разработке кажется приемлемым, в production быстро приводит к деградации производительности, утечкам памяти и росту сетевого трафика.

В production-окружении не используется прямое подключение через unpkg или cdn без контроля версий. Основной подход — сборка через bundler (Vite, Webpack, Rollup).

Установка через npm

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);

Важные параметры:

  • keepBuffer — уменьшает резкие перерисовки при панорамировании
  • updateWhenIdle — снижает нагрузку при активном движении карты
  • crossOrigin — критичен для canvas-операций и export в изображения

Работа с тайловыми провайдерами

Production-система всегда использует внешние или self-hosted tile server’ы. Публичные бесплатные провайдеры не подходят для нагрузки.

Типовая схема:

  • CDN для статических тайлов
  • собственный tile server (Mapnik / Tegola / TileServer GL)
  • fallback слой для деградации качества

Пример:

const baseLayer = L.tileLayer('https://tiles.mycdn.com/base/{z}/{x}/{y}.png', {
  maxZoom: 19,
  subdomains: ['a', 'b', 'c'],
  detectRetina: true
});

Кэширование и повторное использование тайлов

В production важно минимизировать повторные запросы. Используются:

  • HTTP cache headers (Cache-Control, ETag)
  • Service Worker caching
  • CDN edge caching

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-настройки:

  • chunkedLoading снижает блокировку UI
  • отключение hover-эффектов уменьшает reflow
  • минимизация кастомных иконок ускоряет отрисовку

Управление перерисовкой карты

Частая ошибка production — постоянные вызовы invalidateSize и setView без необходимости.

Правильный подход:

requestAnimationFrame(() => {
  map.invalidateSize();
});

Использование debounce:

function debounce(fn, delay) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), delay);
  };
}

Векторные слои и GeoJSON

Работа с GeoJSON должна учитывать размер данных.

L.geoJSON(data, {
  style: {
    weight: 2,
    opacity: 0.8
  },
  onEachFeature: (feature, layer) => {
    layer.bindPopup(feature.properties.name);
  }
}).addTo(map);

Для production важно:

  • упрощение геометрии до загрузки (simplification)
  • разбиение больших файлов на тайлы
  • lazy loading по bbox

Lazy loading геоданных

Загрузка данных по границам карты:

map.on('moveend', () => {
  const bounds = map.getBounds();

  fetch(`/api/geo?bbox=${bounds.toBBoxString()}`)
    .then(r => r.json())
    .then(data => {
      L.geoJSON(data).addTo(map);
    });
});

Обязательная оптимизация — предотвращение повторных запросов при мелких сдвигах.

Производительность DOM и Canvas

Leaflet использует DOM-слои для маркеров и SVG/Canvas для векторных объектов.

Production-настройка:

L.map('map', {
  preferCanvas: true
});

Canvas снижает нагрузку при большом количестве объектов.

CSP и безопасность

В 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'
});

Дополнительно:

  • retry logic на уровне fetch
  • fallback провайдер
  • мониторинг 4xx/5xx ответов

Ограничение частоты обновлений

При анимациях и потоковых данных необходимо ограничивать обновления маркеров:

let lastUpdate = 0;

function update(data) {
  const now = Date.now();
  if (now - lastUpdate < 100) return;
  lastUpdate = now;

  // обновление слоя
}

Архитектура production-карты

Типовая структура:

  • UI слой (React/Vue/vanilla)
  • Map controller (инициализация и lifecycle)
  • Data layer (API, WebSocket)
  • Tile layer manager
  • Cache layer (HTTP + SW)

Разделение критично для масштабируемости и тестирования.

Работа в SSR окружениях

Leaflet не поддерживает server-side rendering напрямую. Любая инициализация карты должна происходить только на клиенте.

if (typeof window !== 'undefined') {
  const map = L.map('map');
}

Инициализация вне этого условия приводит к ошибкам сборки.

Минимизация bundle size

Production требует tree-shaking и исключения неиспользуемых модулей:

  • отключение ненужных контролов
  • исключение legacy плагинов
  • использование динамического импорта слоёв
const HeatLayer = await import('leaflet.heat');

Мониторинг и метрики

В production важно отслеживать:

  • время загрузки тайлов
  • FPS при панорамировании
  • количество DOM-элементов маркеров
  • размер GeoJSON слоёв

Инструменты:

  • Performance API
  • Web Vitals
  • custom telemetry events

Управление памятью

Критические моменты:

  • удаление слоёв через map.removeLayer
  • очистка событий map.off
  • уничтожение контейнера map.remove

Игнорирование этих операций приводит к утечкам при SPA-навигации.