Главное Свежее Вакансии Образование
2 616 4 В избр. Сохранено
Авторизуйтесь
Вход с паролем

Гид по Google Tag Manager — часть 2

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

Содержание:

  • Массивы Javascript
  • Объекты JavaScript
  • DOM-скрапинг
  • Как инициализировать dataLayer
  • Как подгружать информацию в dataLayer
  • Как перезаписать значение существующей переменной
  • Правильный формат данных
  • Правила присвоения имен переменным
  • Event в dataLayer
  • Сниппет GTM
  • Где должен находиться сниппет dataLayer
  • Какую информацию нужно хранить в dataLayer
  • Как понять, что GTM работает?
  • F.A.Q.

Слой данных (он же dataLayer) — это объект JavaScript, который хранит и отправляет данные в GTM. Например, это может быть информация о цене товара, его характеристиках, количестве посетителей и покупок в интернет-магазине.

Пример пустого слоя данных:

<script type="text/javascript">
dataLayer = [];
</script>

Пример слоя данных с переменными:

<script type="text/javascript">
dataLayer = [{
    'pageCategory': 'Purchase',
    'product': 'Socks',
    'color': 'red',
    'amount' : 2,
    'price': 550
}];
</script>

Этот небольшой кусок кода сообщает GTM, что вы продали носки красного цвета. По умолчанию GTM извлекает данные из HTML-страницы и вставляет их в Google Analytics, где вы видите свою статистику продаж.

b_5976b7d4f0eec.jpg

Проблема этого подхода в том, что структура HTML может изменяться без вашего ведома, так как коммерческие сайты постоянно обновляются, в результате чего любой тег может перестать работать в любой момент без уведомления.

Чтобы избежать этого, нужно использовать dataLayer — этот объект будет сохранять всю информацию, которую нужно собрать с веб-страницы. Теперь не важно как будет меняться HTML-структура сайта — эти изменения не коснутся dataLayer, а ваши теги продолжат работать правильно.

b_5976b7d5209ef.jpg

Для того, чтобы лучше понять как работает dataLayer, необходимо сначала познакомиться с объектами и массивами Javascript.

Массивы Javascript

Массив — это особая переменная для хранения данных. Если обычная переменная хранит только одно значение в один момент времени, то массив может хранить несколько элементов одного или разных типов данных одновременно или в разные периоды времени. Например, если a — обычная переменная, то в один момент она может хранить значение 10, а в другой — 20.

a = 10; a = 20;

Массив состоит из одного или нескольких элементов. Ими могут быть: слова (например, hello), числовые значения (например, 10), неопределенные значения, логические значения (true или false), другие массивы или объекты.

Массив можно создать с помощью функции массива или буквенного значения [ ]. Например, так в JavaScript будет выглядеть пустой массив, созданный с помощью функции:

var a=new Array();

Здесь a — имя массива, а var — команда, которая создает переменные в JavaScript.

Массиву можно дать любое имя:

var myArray = new Array();

Так выглядит массив, созданный с помощью буквенного значения []:

var a= [];

Или:

a= [];

В GTM массив слоя данных называют dataLayer. Так будет выглядеть пустой dataLayer:

dataLayer=[];

Поскольку эта строка кода написана на JavaScript, ее необходимо закрыть тегами

...
, чтобы вставить массив в HTML-документ:
<script type="text/javascript">
dataLayer = [];
</script>

Теперь создадим новую переменную массива a, с тремя элементами:

a=["hi","bye","good bye"];

Все эти элементы — hi, bye и good bye — принадлежат к типу string (строки). Такие массивы называются массивами строк. Аналогично и с числовыми значениями, такой массив называется числовым массивом:

a=[10,24,56];

Также в массиве, могут быть разные типы данных одновременно:

a1=["hi","bye","good bye", 10, 20, 56]; 
a2=["hi",10,"bye",20, 56,"good bye"];

Примечание: Даже если a1 и a2 содержат те же самые элементы, это все равно разные переменные массива, так как порядок в них отличается.

Другие примеры массивов:

a=[,,,]; // массив неопределенных значений. Используйте запятые, чтобы задать неопределенное значение.
a=[true,false]; // массив логических значений.
a=[["Clasicos",57],["Zapatos",456]]; // массив массивов, также называется многомерным массивом.
a=[{"id":"20","name":"Zapatos"}]; // массив, который содержит только один объект.
a=[{"id":"20","name":"Zapatos"},{"id":"53","name":"Masculino"}]; // массив с двумя объектами

Пробелы и переносы не имеют значения, поэтому, для более удобного восприятия, тот же самый массив проще записать так:

a=[
{"id":"20","name":"Zapatos"},
{"id":"53","name":"Masculino"},
];

Пример массива с несколькими объектами:

a=[
{"id":"20","name":"Zapatos"},
{"id":"53","name":"Masculino"},
{"id":"57","name":"Clasicos"},
{"id":"138","name":"Deportes"},
{"id":"139","name":"Masculino"}
{"id":"201","name":"Zapatos"},
{"id":"1244","name":"Mocasines"},
{"id":"1340","name":"Apaches"}
];

Типы данных, которые могут содержаться в массиве

Также вы можете создать массив, который содержит элементы всех возможных типов данных:

a=["size", 10, true,,["Clasicos",57],{"id":"20","name":"Zapatos"}];

Size — элемент типа строка.

10 — элемент типа число.

True — логическое значение.

,, или пустое значение — элемент массива неопределенного типа.

["Clasicos",57] — элемент массива, который также является массивом.

{"id":"20","name":"Zapatos«} — элемент массива типа объект.

Объекты JavaScript

Объект JavaScript — это переменная со свойствами и методами. Так, например, выглядит синтаксис, который создает объект JavaScript:

object_name = {"property1":value1, "property2":value2, ....."propertyN":valueN};

Или:

object_name = {"key1":value1, "key2":value2, ....."keyN":valueN};

В этом случае свойство может быть строкой или идентификатором. Идентификатор — это ключевое слово с особым значением, которое нельзя использовать в качестве имени переменной. Например, event (событие) — идентификатор. Значением свойства может быть:

• строка;• числовое значение;• неопределенное значение;• логическое значение (true, false); • массив;• многомерный массив;• объект;• массив объектов.

Теперь создадим пустой объект без свойств:

a = {};

Здесь a — это имя объекта JavaScript, но вообще вы можете называть его как угодно. Например, создадим объект с именем user (пользователь) и добавим к нему четыре свойства:

user={"firstName":"John", "lastName":"Marshall", "age":85, "eyeColor":"blue"};

Для более удобного восприятия, тот же самый код проще записать так:

user={
"firstName":"John",
"lastName":"Marshall",
"age":45,
"eyeColor":"blue"
};

Другие примеры объектов JavaScript:

a = {"color":"#fu00e9"};

color (цвет) — это свойство объекта a.

#fu00e9 — значение свойства color.

a = {"price":164900.00};

price (цена) — свойство объекта а.

164900.00 — значение свойства price.

a = {"color":"#fu00e9", "price":164900.00};

Здесь у объекта a сразу два свойства — color и price.

Аналогично, можно определить объект с помощью нескольких свойств:

a = {
    "color": "#fu00e9", // значение типа "строка"
    "price": 164900.00, // числовое значение
    "sizeList": null, // неопределенное значение
    "visitorsHasOrders": true, // логическое значение
    "data": [45, 67, 89, 20], // значение типа "массив"
    "shoppingCart": [["Clasicos", 57], ["Zapatos", 456]], // значение типа "многомерный массив"
    "pageAttributes": {"page": "product"}, // значение типа "объект"
    "pageCategory": [
        // значение типа "массив объектов"
        {"id": "20", "name": "Zapatos"}, {"id": "53", "name": "Masculino"}, {"id": "57", "name": "Clasicos"}, {"id": "138", "name": "Deportes"}, {"id": "139", "name": "Masculino"}, {"id": "201", "name": "Zapatos"}, {"id": "1244", "name": "Mocasines"}, {"id": "1340", "name": "Apaches"}
     ] };

DataLayer может быть еще сложнее, если он формируется на стороне сервера. Как в этом случае:

<script type="text/javascript"><?php if($_SERVER['SCRIPT_NAME'] == 'thank-you.php') {?>

    dataLayer = [{
        // ID Транзации. Тип: строка. Обязательно.
        'transactionId': <?=$transaction['trans_id']?>,
        // Имя магазина. Тип: строка. Опционально.
        'transactionAffiliation': <?=$transaction['store_name']?>,
        // Общая выручка. Тип: число. Обязательно.
        'transactionTotal': <?=$transaction['revenue']?>,
        // Сумма налога на транзакцию. Тип: число. Опционально.
        'transactionTax': <?=$transaction['tax']?>,
        // Стоимость доставки. Тип: число. Опционально.
        'transactionShipping': <?=$transaction['shipping_cost']?>,
        'transactionProducts': [
            <?php   $flag = false;
            $n = sizeof($products_array);
            for($i = 0;$i < $n;$i++) {
            ?><?php if($flag) { ?>,<?php } ?><?php $flag = true; ?>
            {
        // Артикул продукта. Тип: строка. Обязательно.
        'sku': '<?=$products_array[$i]['sku']?>',
        // Название продукта. Тип: строка. Обязательно.
        'name': '<?=$products_array[$i]['name']?>,
        // Категория продукта. Тип: строка. Опционально.
        'category': '<?=$products_array[$i]['category']?>',
        // Цена продукта. Тип: число. Обязательно.
        'price': '<?=$products_array[$i]['price']?>',
        // Количество продукта. Тип: число. Обязательно.
        'quantity': '<?=$products_array[$i]['quantity']?>'
            }
            <?php } ?>
        ]
    }];

    <?php } ?></script>

Запомните:

a=[]; — создает массив.

a={}; — создает объект.

a=[{}]; — создает массив с одним объектом.

DOM-скрапинг

Это техника извлечения данных напрямую из HTML-элементов веб-страницы (кнопок, ссылок, форм и т. д.) с помощью JavaScript и знаний HTML DOM. Например, этот код извлекает с сайта информацию о цвете какого-то товара с id product_45, чтобы передать эти данные в нужное вам место — CRM, Google Analytics и т. д.

<script type="text/javascript">
var product_color = document.getElementById("div#product_45 > span.color").innerHTML;
</script>

DOM-скрапинг полезен, когда нет времени оперативно запустить надежный сбор данных, чтобы настроить трекинг. С помощью DOM-скрапинга вы сможете быстрее собирать данные со страницы, поскольку практически не будете зависеть от веб-разработчика.

Однако, стоит понимать, что решение, внедренное через DOM-скрапинг достаточно нестабильно, так как HTML-структура страницы постоянно меняется. Поэтому на него нельзя полагаться в течение долгого времени, а также, если на кону большие суммы денег (сайты авиакомпаний, например).

Как инициализировать dataLayer

Инициализация — это, по сути, и есть создание dataLayer, указание всех переменных, их свойств и значений.

Так выглядит dataLayer без переменных:

<script type="text/javascript">
dataLayer = [{}];
</script>

DataLayer с одной переменной:

<script type="text/javascript">
dataLayer = [{'pageCategory': 'Statistics'}];
</script>

DataLayer с тремя переменными:

<script type="text/javascript">
dataLayer = [{
    'pageCategory': 'Statistics',
    'visitorType': 'high-value',
    'event':'customizeCart'
}];
</script>

В этих примерах, pageCategory, visitorType и event — переменные, они же свойства объекта. Statisitcs, high-value и customizeCart — значения переменных.

Чтобы изменить, удалить или добавить информацию в dataLayer, вам, соответственно, нужно изменить значение переменной, удалить или добавить новую переменную.

Как подгружать информацию в dataLayer

Если dataLayer уже инициализирован на странице, определенная часть информации уже находится в нем. Все остальные данные могут подгружаться динамически во время работы с помощью метода push.

Также через push вы сможете динамически добавлять объекты в dataLayer, которые содержат одну или более переменных.

Пример метода push:

dataLayer.push({'variable_name': 'variable_value'});

Представим, что этот код уже присутствует на странице:

<script type="text/javascript">
dataLayer = [{'pageCategory': 'Statistics'}];
</script>

Он уже содержит одну переменную. И если использовать push, как в этом примере:

<script type="text/javascript">
dataLayer.push({'visitorType': 'high-value'});
</script>

То dataLayer будет выглядеть так:

<script type="text/javascript">
dataLayer = [{'pageCategory': 'Statistics', 'visitorType': 'high-value'}];
</script>

Как перезаписать значение существующей переменной

Внести переменную с таким же именем, но с новым значением в dataLayer. Допустим, что это ваш текущий dataLayer:

<script type="text/javascript">
dataLayer = [{'pageCategory': 'Statistics', 'visitorType': 'high-value'}];
</script>

И если использовать метод push, как в этом примере:

<script type="text/javascript">
dataLayer.push({'pageCategory': 'Maths'});
</script>

То значение переменной pageCategory перезапишется, и dataLayer будет выглядеть так:

<script type="text/javascript">
dataLayer = [{'pageCategory': 'Maths', 'visitorType': 'high-value'}];
</script>

Правильный формат данных

Когда вы загружаете информацию в dataLayer из фронтенд или бекенд части сайта, убедитесь, что она в правильном формате, который обрабатывает GTM. Например, если вы попытаетесь загрузить в dataLayer переменные e-commerce, которые не распознаются или не рекомендуются GTM, то ваша система отслеживания электронной торговли не будет работать.

<script type="text/javascript">
dataLayer = [{
    'Transaction Id': '1234',
    ...
}];
</script>

В этом случае GTM не сможет распознать Transaction Id как переменную электронной торговли, потому что между словами стоит пробел. Правильный код выглядит так:

<script type="text/javascript">
dataLayer = [{
    'transactionId': '1234',
    ...
}];
</script>

‘transactionId’ — пример идентификатора, поэтому у него особое значение и он не может использоваться в качестве обычного имени переменной в GTM.

Правила присвоения имен переменным

Если вы по разному называете одни и те же переменные на разных страницах, GTM не запустит теги во всех необходимых местах. Например, если вы на главной странице установите ее тип с помощью переменной pageCategory, то и на других страницах нужно будет использовать точно такое же название — Pagecategory или PageCategory уже не подойдет, потому что имена переменных чувствительны к регистру.

Такой формат работать не будет:

// страница входа 
dataLayer.push({'pageCategory': 'members-signup'}); 
// страница покупки товара: 
dataLayer.push({'PageCategory': 'product-purchase'});

Не забывайте заключать переменные в кавычки:

dataLayer.push({pageCategory: 'members-signup'}); // Не будет работать
dataLayer.push({'pageCategory': 'members-signup'}); // Работает

Код JavaScript тоже чувствителен к регистру, поэтому не путайте dataLayer и datalayer.

datalayer.push({'pagecategory': 'members-signup'}); // Не будет работать
dataLayer.push({'pageCategory': 'members-signup'}); // Работает

Event в dataLayer

Это особая переменная, которая используется с обработчиками событий в JavaScript. Она нужна для запуска тега, когда пользователь взаимодействует с разными элементами на странице: формами, кнопками, ссылками.

Синтаксис выглядит так:

dataLayer.push({'event': 'event_name'});

Здесь event и event name — строки.

Например, следующий код обновляет переменную event, когда пользователь нажимает на кнопку:

<a href="https://www.optimizesmart.com/download" onclick="dataLayer.push({'event': 'pdf-download'});">PDF download</a>

Другие примеры особых переменных dataLayer:

• hitType;

• nonInteraction;

• ecommerce.

Сниппет GTM

Это кусок кода, который устанавливает GTM к вам на сайт. А уже сам GTM содержит все маркетинговые и аналитические теги, триггеры и переменные.

Пример сниппета GTM:

<!-- Google Tag Manager --><noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-XXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript><script type="text/javascript">
(function (w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({
      'gtm.start': new Date().getTime(), event: 'gtm.js'
});
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', 'GTM-XXXX');
</script><!-- End Google Tag Manager -->

Этот код нужно поместить на каждую страницу вашего сайта.

Где должен находиться сниппет dataLayer

Он всегда должен быть выше сниппета GTM в части head (...).

Например:

<script type="text/javascript">
dataLayer = [{'pageCategory': 'Statistics', 'visitorType': 'high-value'}];
</script><!-- Google Tag Manager --><script type="text/javascript">
(function (w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({
    'gtm.start': new Date().getTime(), event: 'gtm.js'
});
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', 'GTM-XXXX');
</script><!-- End Google Tag Manager --><!-- Google Tag Manager --><noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-XXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript><!-- End Google Tag Manager -->

Если вы поместите сниппет dataLayer ниже сниппета GTM, то вся система не будет работать, например, как в этом случае:

<!-- Google Tag Manager -->
...
<!-- End Google Tag Manager --><script type="text/javascript">
dataLayer = [{'pageCategory': 'Statistics','visitorType': 'high-value'}];
</script>

В этом коде переменные pageCategory и visitorType будут недоступны для GTM.

Какую информацию нужно хранить в dataLayer

Это самый важный вопрос. Чтобы избежать хлопот с изменением слоев каждый раз, когда вы используете новый тег-менеджер, вам стоит внедрить универсальные слои данныхна каждой странице вашего сайта. Каждый универсальный dataLayer должен содержать все ключевые атрибуты веб-страницы, в которую он вписан.

1. Свойства страницы:

• название страницы;• URL страницы;• тип страницы и т. д.

2. Свойства продукта:

• имя;• ID продукта;• цена;• тип продукта;• URL изображения продукта;• вариация выбранного продукта (цвет, размер);• выбранное количество продукта;• количество просмотров;• количество кликов на продукт;• доступность (в наличии или нет);• купон;• использованный купон.

3. Свойства пользователя:

• ID пользователя;• ID клиента;• тип пользователя (залогиненный, разлогиненный, постоянный клиент и т. д.);• поведение пользователя;• предпочтения пользователя;• история покупок;• действия пользователя (нажал кнопку, отправил форму, подписался, купил и т. д.);• веб-браузер;• операционная система;• устройство (компьютер, телефон, планшет).

Любая информация, которая может быть полезна сегодня или в ближайшем будущем, должна содержаться в универсальном слое данных. Например:

dataLayer = [{
    "color": "#fu00e9",
    "deviceAgent": "Mozilla 5.0"
    "deviceOs": "non-mobile",
    "deviceTheme": "desktop",
    "deviceType": "desktop",
    "gender": "Masculino",
    "googleRemarketingLabel": "",
    "image": "www.static.dafyty.com.com/brahma-6338-0822-1-product.jpg",
    "pageAttributes": {"page": "product"},
    "pageBrand": [{"id": "2", "name": "Brahma"}],
    "pageCategory": [
        {"id": "20", "name": "Zapatos"},
        {"id": "53", "name": "Masculino"},
        {"id": "57", "name": "Clasicos"},
        {"id": "138", "name": "Deportes"},
        {"id": "139", "name": "Masculino"},
        {"id": "201", "name": "Zapatos"},
        {"id": "1244", "name": "Mocasines"},
        {"id": "1340", "name": "Apaches"}
    ],
    "pageMainCategory": {"id": "1340", "name": "Apaches"},
    "pageName": "product",
    "pageProductDescription": "Un calzado muy comodo que puedes combinar tanto con tus atuendos formales como informales.",
    "pageProductName": "Zapatos Brahma Cafe",
    "pageTitle": "",
    "price": "164900.00",
    "priceDiscount": "12%",
    "season": "Todas las temporadas",
    "seasonYear": "2012",
    "shopAnalyticsAccount": "",
    "shopName": "default",
    "sizeList": "",
    "sku": "BR002SH19DJS",
    "specialPrice": "144900.00",
    "urlPageProduct": "www.dafiti.com.com/Zapatos-Brahma-Cafe-280.html" }];

А так выглядит слой данных в консоли инструментария разработчика Google (Google Developers):

b_5976b7d54b3d3.jpg

Если вы внимательно посмотрите на скриншот, то увидите, что все переменные перечислены в алфавитном порядке.

Слой данных, представленный выше, содержит несколько динамических переменных ближе к концу, например: price (цена), specialPrice (цена со скидкой). Эти переменные не были закодированы на странице изначально, а динамически подгружены в dataLayer с помощью метода push.

Как понять, что GTM работает

В Google Chrome нажмите правой кнопкой мыши на веб-странице и выберите пункт меню «Просмотреть код». Откроется окно разработчика в нижней части вашего браузера.

Нажмите на вкладку «консоль» (console) в верхней части окна инструментов, введите dataLayerв командной строке и нажмите Enter.

b_5976b7d57509b.jpg

Теперь вы увидите несколько объектов JavaScript:

b_5976b7d59ad0b.jpg

gtm.js — библиотека javascript. Если этого файла нет, ваш тег менеджер не будет работать.

gtm.dom — файл DOM. Если этого файла нет, значит не загружен DOM.

gtm.load — загрузочный файл. Если этого файла нет, значит не загружено окно и все его содержимое.

Эти события запускаются в следующем порядке: gtm.js → gtm.dom → gtm.load. Если dataLayer не получает данные всех перечисленных событий — GTM неисправен.

Если на вашей странице закодирован dataLayer, то окно инструментария разработчика покажет вам как минимум три объекта JavaScript. Один объект — ваш слой данных, а остальные три — объекты событий JavaScript по умолчанию:

b_5976b7d5c42b0.jpg

F.A.Q.

Может ли у каждой страницы быть свой уникальный dataLayer?Да, не только может, но и должен.

Нужно ли использовать одни и те же переменные в dataLayer на каждой странице?Нет.

Когда обновляется dataLayer в случае с отслеживаемым событием?Когда это событие происходит, например, пользователь кликает по кнопке, отправляет форму или загружает страницу.

Какие ограничения у переменных dataLayer?Переменные dataLayer привязаны к странице, это значит, что они существуют до тех пор, пока пользователь остается на странице.

Как объявить переменные dataLayer, которые действительны более, чем на одной странице?Используя те же самые переменные на нескольких страницах вашего сайта.

Можно ли переименовывать dataLayer?Да, изменять имя dataLayer нужно, когда на одной странице работает несколько кодов оболочки GTM, и вы не хотите, чтобы каждый контейнер использовал один и тот же dataLayer.

Остались вопросы о работе GTM? Задавайте их в нашем телеграм-чате, я постараюсь ответить на все.

b_5976b7c5ce0bc.jpg

Автор: Дмитрий Еремеев, руководитель отдела развития проектов

+2
В избр. Сохранено
Авторизуйтесь
Вход с паролем
Комментарии
Дмитрий 75251
В разделе "Как подгружать информацию в dataLayer" ошибки.
Если есть код
dataLayer = [{'pageCategory': 'Statistics'}];
затем выполняется
dataLayer.push({'visitorType': 'high-value'});
результат будет не
dataLayer = [{'pageCategory': 'Statistics', 'visitorType': 'high-value'}];
а
dataLayer = [{'pageCategory': 'Statistics'}, {'visitorType': 'high-value'}];
Пример с перезаписью значение существующей переменной, тоже неверный будет:
dataLayer = [{'pageCategory': 'Statistics', 'visitorType': 'high-value'},{'pageCategory': 'Maths'}];
Ответить
Дима Еремеев
вы правы. в следующей редакции мы обязательно это поправим
спасибо
Ответить
Иван Прокопенко
dataLayer.push({pageCategory: 'members-signup'}); // Не будет работать
dataLayer.push({'pageCategory': 'members-signup'}); // Работает
Откуда такой вывод? Ключ в объекте можно не заворачивать в кавычки, эти записи идентичные.
Ответить
Дима Еремеев
вы правы. действительно код будет работать.
тут, наверное, стоит лишь убрать комментарий. но оставить рекомендацию принудительного использования кавычек, чтобы использовать пробелы и т д
спасибо за замечание
Ответить
Выбрать файл
Блог проекта
Расскажите историю о создании или развитии проекта, поиске команды, проблемах и решениях
Написать
Личный блог
Продвигайте свои услуги или личный бренд через интересные кейсы и статьи
Написать

Spark использует cookie-файлы. С их помощью мы улучшаем работу нашего сайта и ваше взаимодействие с ним.