Skip to main content

A/B-сплит лендингов через JS-редирект

Назначение

Скрипт делит трафик с одного лендинга между несколькими вариантами (A/B/C…). Все GET-параметры (UTM и прочие) и #hash сохраняются. Выпавший вариант закрепляется за посетителем.

Куда ставить

  • Только на исходную страницу (вариант A).
  • В <head> страницы (настройки страницы → код в head). Если вставить скрипт в HTML-блок, перед редиректом будет виден контент.
  • На страницы других вариантов (B, C…) скрипт не ставится.

Настройки

Переменная Назначение
SOURCE_PATH Путь страницы, на которой запускается сплит (без домена). На других страницах скрипт не срабатывает, это защищает от цикла.
STORAGE_KEY Имя ключа в localStorage, где хранится выпавший вариант. Уникален для каждого теста. Смена ключа перераспределяет всех заново.
VARIANTS Список вариантов: name, url, weight. Вес задаёт долю трафика (2,1,1 = 50/25/25).

Как работает

  1. Если путь ≠ SOURCE_PATH, скрипт останавливается.
  2. Страница скрывается, чтобы не было мелькания. Через 3 сек срабатывает страховочный показ.
  3. Из localStorage берётся сохранённый вариант. Если его нет, вариант выбирается случайно с учётом весов и сохраняется.
  4. Если выпал вариант A (текущая страница), страница показывается без редиректа.
  5. Иначе происходит редирект (location.replace) на URL варианта со всеми параметрами и hash.

Где хранится вариант

  • В localStorage браузера посетителя, на сервере ничего нет.
  • Выбор привязан к браузеру, устройству и домену. С другого устройства, в инкогнито или после очистки данных посетитель распределяется заново.
  • Хранится бессрочно.
  • Проверить или сбросить: DevTools → Application → Local Storage → домен → ключ STORAGE_KEY. Для своих тестов удобнее режим инкогнито.

Ограничения

  • Целевые URL должны иметь путь, отличный от SOURCE_PATH, иначе будет цикл.
  • Метка группы в URL не передаётся. Вариант определяется по адресу лендинга.

Скрипт (пример: intensiv)

<script>
(function () {
  // ===== НАСТРОЙКИ =====
  var SOURCE_PATH = '/intensiv-a';
  var STORAGE_KEY = 'ab_intensiv_2026_10';

  var VARIANTS = [
    { name: 'a', url: 'https://edu.baniilatreaba.ro/intensiv-a', weight: 1 },
    { name: 'b', url: 'https://edu.baniilatreaba.ro/intensiv-b', weight: 1 },
    { name: 'c', url: 'https://edu.baniilatreaba.ro/intensiv-c', weight: 1 }
  ];
  // =====================

  var current = new URL(window.location.href);
  if (current.pathname !== SOURCE_PATH) return;

  // Сразу скрываем страницу
  var html = document.documentElement;
  html.style.visibility = 'hidden';
  function show() { html.style.visibility = ''; }
  // Страховка: показать через 3 сек
  var failsafe = setTimeout(show, 3000);

  var saved = null;
  try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) {}
  var variant = VARIANTS.filter(function (v) { return v.name === saved; })[0];

  if (!variant) {
    var total = VARIANTS.reduce(function (s, v) { return s + (v.weight || 1); }, 0);
    var r = Math.random() * total;
    for (var i = 0; i < VARIANTS.length; i++) {
      r -= (VARIANTS[i].weight || 1);
      if (r < 0) { variant = VARIANTS[i]; break; }
    }
    variant = variant || VARIANTS[0];
    try { localStorage.setItem(STORAGE_KEY, variant.name); } catch (e) {}
  }

  var target = new URL(variant.url, current.origin);

  // Вариант = текущая страница → показываем и остаёмся
  if (target.origin === current.origin && target.pathname === current.pathname) {
    clearTimeout(failsafe);
    show();
    return;
  }

  // Переносим все параметры и hash
  current.searchParams.forEach(function (value, key) {
    target.searchParams.append(key, value);
  });
  target.hash = current.hash;

  window.location.replace(target.toString());
})();
</script>