Hydration в контексте Mapbox GL JS обозначает процесс восстановления интерактивной карты на клиенте после серверного рендеринга или первичной отрисовки статического контейнера. В отличие от классических DOM-приложений, карта представляет собой не только разметку, но и WebGL-контекст, состояние камеры, загруженные тайлы, стиль, источники данных и набор слоёв. Поэтому «гидратация» здесь означает не только повторную инициализацию, но и аккуратное восстановление всего графа состояния карты без пересоздания логики с нуля.
Карта в Mapbox GL JS не является декларативным UI в чистом виде.
Каждый экземпляр map инкапсулирует:
При серверном рендеринге можно отдать только статический контейнер:
<div id="map"></div>
На клиенте этот контейнер становится точкой гидратации, где создаётся полноценный объект карты:
import mapboxgl from "mapbox-gl";
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/streets-v12",
center: [69.6, 42.3],
zoom: 10
});
Проблема возникает, если на сервере уже был сформирован визуальный «снимок» состояния: камера, слои, фильтры. Клиентская инициализация должна восстановить это состояние без расхождений.
Mapbox GL JS зависит от браузерных API:
windowdocumentПри SSR эти сущности отсутствуют, поэтому прямое создание карты приводит к ошибкам. Гидратация всегда переносится в клиентский слой:
let map;
if (typeof window !== "undefined") {
map = new mapboxgl.Map({
container: "map",
style: window.__INITIAL_MAP_STYLE__,
center: window.__INITIAL_CENTER__,
zoom: window.__INITIAL_ZOOM__
});
}
Ключевой принцип: сервер передаёт только сериализованное состояние, клиент восстанавливает интерактивность.
Для полноценной гидратации необходимо сохранять минимальный набор параметров:
Пример структуры:
window.__MAP_STATE__ = {
style: "mapbox://styles/mapbox/dark-v11",
center: [69.6, 42.3],
zoom: 11.2,
bearing: 20,
pitch: 35,
activeLayers: ["roads", "buildings"]
};
При гидратации критично избежать визуального «прыжка» карты. Типичная
ошибка — инициализация с дефолтными значениями и последующий
setView.
Корректный подход — передача состояния сразу в конструктор:
const state = window.__MAP_STATE__;
const map = new mapboxgl.Map({
container: "map",
style: state.style,
center: state.center,
zoom: state.zoom,
bearing: state.bearing,
pitch: state.pitch,
preserveDrawingBuffer: true
});
Если состояние доступно только после инициализации, используется блокировка рендера:
map.once("load", () => {
map.jumpTo({
center: state.center,
zoom: state.zoom,
bearing: state.bearing,
pitch: state.pitch
});
});
Однако jumpTo после первого кадра создаёт дополнительный
рендер, поэтому предпочтителен ранний injection состояния.
Слои и источники в Mapbox GL JS не являются автоматически синхронизируемыми сущностями. При восстановлении состояния важно соблюдать порядок:
addSource)addLayer)Пример:
map.on("load", () => {
const state = window.__MAP_STATE__;
state.sources.forEach(src => {
map.addSource(src.id, src.data);
});
state.layers.forEach(layer => {
map.addLayer(layer);
});
state.layers.forEach(layer => {
if (layer.visibility === "none") {
map.setLayoutProperty(layer.id, "visibility", "none");
}
});
});
Частая ошибка гидратации — повторное создание карты в одном контейнере. WebGL контекст не допускает безопасного «перезапуска» без освобождения ресурсов.
Перед созданием нового экземпляра требуется явное уничтожение:
if (map) {
map.remove();
}
remove() освобождает:
Игнорирование этого шага приводит к утечкам памяти и блокировке GPU ресурсов.
Гидратация подразумевает согласование двух моделей состояния:
Конфликт возникает, если стиль изменился между рендером и гидратацией. В этом случае Mapbox пересобирает граф слоёв, что может привести к мерцанию.
Для стабилизации используется фиксация версии стиля:
const style = window.__MAP_STATE__.styleVersioned;
map.setStyle(style);
После setStyle необходимо повторно восстановить
источники и слои, так как стиль сбрасывает их состояние.
Стиль в Mapbox GL JS загружается асинхронно. Это создаёт гонку между:
Корректная схема:
map.on("style.load", () => {
restoreSources(map);
restoreLayers(map);
restoreCamera(map);
});
Событие style.load является точкой, где граф рендера
гарантированно готов к модификации.
В SPA-архитектурах контейнер карты может уничтожаться и создаваться повторно. При этом важно различать:
Если контейнер сохраняется, возможен паттерн «reuse instance»:
if (map.getContainer().isConnected) {
map.resize();
} else {
map = createMap();
}
resize() критичен при изменении размеров контейнера
после гидратации, особенно при flex/grid layout.
Инициализация карты может быть отложена до появления контейнера в viewport. Это уменьшает стоимость первичного рендера:
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
initMap();
observer.disconnect();
}
});
observer.observe(document.getElementById("map"));
Такой подход снижает нагрузку на WebGL и ускоряет гидратацию страницы в целом.
При сложных картах важно проверять консистентность:
if (map.getLayer("roads")) {
map.setFilter("roads", ["==", "type", "primary"]);
}
Отсутствие проверок приводит к ошибкам гидратации при частичной загрузке.
Камера является наиболее чувствительной частью состояния. Любое расхождение между серверным и клиентским значением вызывает скачки.
Минимизация артефактов достигается через:
essential: truemap.jumpTo({
center: state.center,
zoom: state.zoom,
essential: true
});
Гидратация в Mapbox GL JS сводится к согласованному восстановлению трёх уровней:
Ключевой принцип — отсутствие «пересборки с нуля» при наличии возможности восстановить уже сериализованное состояние, минимизируя повторную инициализацию WebGL и сохраняя непрерывность визуального контекста.