Server-side rendering

Общая модель работы SSR и роль Tom Select

Server-side rendering в контексте использования Tom Select представляет собой подход, при котором базовая разметка <select> и его опций формируется на сервере, а клиентская библиотека выполняет только инициализацию поведения, расширяя уже готовую DOM-структуру. Такой подход снижает нагрузку на клиент, улучшает индексируемость и обеспечивает предсказуемое отображение до выполнения JavaScript.

Tom Select не требует обязательного SSR, но корректно работает в сценариях, где HTML уже содержит заранее сформированный список <option> элементов. В этом случае библиотека не создает данные с нуля, а «подхватывает» существующую структуру.

Ключевая идея: SSR отвечает за данные и разметку, Tom Select — за интерактивность.


Базовая SSR-разметка для Tom Select

На стороне сервера формируется стандартный HTML-элемент:

<select id="countries" multiple>
  <option value="kz">Kazakhstan</option>
  <option value="ru">Russia</option>
  <option value="de">Germany</option>
  <option value="jp">Japan</option>
</select>

После гидратации на клиенте Tom Select превращает этот элемент в интерактивный компонент:

new TomSelect("#countries");

В SSR-режиме важно, что:

  • все option уже присутствуют в DOM
  • значения value строго определены
  • текстовые метки не требуют дополнительной загрузки

Гидратация существующего DOM

Tom Select при инициализации проходит по DOM-структуре <select> и строит внутренние индексы:

  • список опций
  • отображаемые лейблы
  • состояние выбранных значений
  • поисковый индекс

SSR-данные преобразуются в внутренний формат без дополнительных запросов.

Пример:

<select id="tags" multiple>
  <option value="js" selected>JavaScript</option>
  <option value="ts">TypeScript</option>
  <option value="css">CSS</option>
</select>
new TomSelect("#tags");

Результат инициализации:

  • JavaScript уже выбран
  • состояние синхронизировано с DOM
  • внутренний state совпадает с серверной разметкой

SSR и предзагрузка выбранных значений

Одним из ключевых сценариев SSR является передача предварительно выбранных значений.

Сервер генерирует:

<select id="users" multiple>
  <option value="1" selected>Alex</option>
  <option value="2">Maria</option>
  <option value="3" selected>John</option>
</select>

Tom Select при инициализации:

new TomSelect("#users");

Поведение:

  • выбранные значения не перезаписываются
  • внутренний state синхронизируется с selected
  • не происходит повторного вычисления defaultValue

Ключевой принцип: DOM является источником истины при SSR.


Асинхронная загрузка и SSR-гибрид

SSR часто комбинируется с динамической загрузкой данных. В этом случае сервер предоставляет начальный набор опций, а клиент расширяет их через API.

<select id="products">
  <option value="100">Laptop</option>
  <option value="101">Phone</option>
</select>
new TomSelect("#products", {
  load: function(query, callback) {
    fetch(`/api/products?q=${encodeURIComponent(query)}`)
      .then(res => res.json())
      .then(data => {
        callback(data.items);
      });
  }
});

Особенность SSR-гибрида:

  • первичный рендер — мгновенный
  • последующие данные подгружаются асинхронно
  • SSR обеспечивает SEO и UX первого экрана

Проблема повторной инициализации (hydration duplication)

Одной из частых ошибок SSR-интеграции является повторное создание экземпляра Tom Select на уже обработанном элементе.

Проблемный сценарий:

new TomSelect("#countries");
new TomSelect("#countries");

Последствия:

  • дублирование DOM-обертки
  • утечка событий
  • некорректный state selection

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

if (!document.querySelector("#countries").tomselect) {
  new TomSelect("#countries");
}

Tom Select сохраняет ссылку на инстанс в DOM-узле, что позволяет проверять состояние инициализации.


SSR и работа с disabled состояниями

Сервер может передавать состояние недоступности элементов:

<select id="payment">
  <option value="card">Card</option>
  <option value="crypto" disabled>Crypto</option>
  <option value="cash">Cash</option>
</select>

Tom Select сохраняет это поведение:

  • disabled опции не участвуют в поиске
  • не могут быть выбраны программно через UI
  • остаются в DOM для консистентности SSR

SSR и кастомные render-функции

Tom Select позволяет переопределять отображение опций. При SSR важно, что сервер генерирует только базовую структуру, а клиент отвечает за визуализацию.

new TomSelect("#countries", {
  render: {
    option: function(data, escape) {
      return `<div>
        <span class="label">${escape(data.text)}</span>
      </div>`;
    }
  }
});

SSR при этом поставляет:

<option value="kz">Kazakhstan</option>

Render-функции:

  • не изменяют исходный DOM
  • применяются только в dropdown UI
  • не влияют на value-state

SSR и preload через data-* атрибуты

Иногда сервер передает дополнительные данные через атрибуты:

<option value="kz" data-region="asia">Kazakhstan</option>

Tom Select считывает эти данные и добавляет их в dataset:

new TomSelect("#countries", {
  render: {
    option: function(data, escape) {
      return `<div>
        ${escape(data.text)}
        <small>${escape(data.region)}</small>
      </div>`;
    }
  }
});

SSR-данные расширяют клиентскую модель без дополнительных запросов.


SSR и контроль состояния формы

Tom Select интегрируется с <form> без потери SSR-состояния.

<form>
  <select id="lang" name="lang">
    <option value="en" selected>English</option>
    <option value="ru">Russian</option>
  </select>
</form>

После инициализации:

new TomSelect("#lang");

Отправка формы:

  • используется актуальное значение Tom Select
  • синхронизация с hidden input происходит автоматически
  • SSR selected сохраняется до изменения пользователем

SSR и динамическое обновление опций

SSR не исключает возможность изменения набора данных после загрузки страницы.

const ts = new TomSelect("#countries");

ts.addOption({ value: "fr", text: "France" });
ts.refreshOptions(false);

Важная особенность:

  • SSR задает начальное состояние
  • клиент может расширять список
  • DOM остается источником синхронизации

SSR и очистка состояния

При удалении компонента важно сбросить состояние, иначе SSR-инициализированные элементы могут оставить «следы» в памяти.

const ts = new TomSelect("#countries");

ts.destroy();

После destroy:

  • восстанавливается исходный <select>
  • удаляются event listeners
  • SSR-разметка снова становится основной

SSR и повторная синхронизация данных

В сложных приложениях возможно несоответствие между серверной разметкой и клиентскими данными.

Пример:

Сервер:

<option value="1" selected>A</option>

Клиент получает обновленные данные API:

ts.clear();
ts.addOption({ value: "2", text: "B" });
ts.addItem("2");

Tom Select не требует пересоздания компонента — SSR используется только как bootstrap-слой.


SSR в условиях частичного рендера страницы

При использовании частичного SSR (например, обновление фрагментов страницы без перезагрузки) важно переинициализировать Tom Select только на новых узлах.

document.querySelectorAll("select").forEach(el => {
  if (!el.tomselect) {
    new TomSelect(el);
  }
});

Это предотвращает:

  • повторную гидратацию
  • конфликт состояний
  • дублирование UI

SSR и производительность

Использование SSR с Tom Select влияет на производительность в нескольких аспектах:

  • уменьшение времени до первого отображения
  • отсутствие необходимости первичной загрузки данных
  • снижение количества API-запросов на старте

Однако при большом количестве <option> необходимо учитывать:

  • размер HTML-ответа
  • время парсинга DOM
  • стоимость инициализации индексов Tom Select

В таких случаях часто применяется компромисс:

  • SSR только первых N элементов
  • остальное через load()

SSR и предсказуемость состояния UI

Одним из ключевых преимуществ SSR является детерминированность состояния:

  • сервер задает начальное значение
  • клиент не «перерешает» выбор
  • визуальное состояние совпадает с данными формы

Tom Select в этом сценарии выступает как слой поведения поверх уже стабильной модели данных.