static.tildacdn.com: это CDN Тильды, и домен собирается в браузере
`static.tildacdn.com` — домен, с которого Тильда отдаёт свой JavaScript, CSS и картинки: на снятой странице магазина оттуда приезжают 20 файлов из 21. Домен не зашит в код константой — он собирается из строки `https://static.tildacdn.` и зоны, которую рантайм читает из одного атрибута в разметке. Рядом работают ещё семнадцать адресов, у каждого своя задача, а у форм, магазина и доставки — правило: при отказе переключиться на дублёра ровно один раз.
Короткий ответ
static.tildacdn.com — домен, с которого Тильда отдаёт свой код и картинки. На
снятой странице магазина оттуда приезжают 20 файлов из 21 и все изображения.
Зону в адресе выбирает атрибут data-tilda-root-zone у тега #allrecords, по
умолчанию com. Рядом ещё семнадцать адресов: оптимизатор картинок, формы,
магазин, доставка, два геосервиса, кабинет покупателя, спасательный код — и по
дублёру у пяти из них.
Домен не константа: его собирает рантайм
В коде нет строки https://static.tildacdn.com. Есть строка без зоны, к которой
зона приклеивается:
"https://static.tildacdn." + t_store__getRootZone() + "/js/" + n + ".min.js"
Саму зону даёт одна функция, и она читает одну вещь из разметки:
function t_getRootZone() {
var t = document.getElementById("allrecords");
return t && t.getAttribute("data-tilda-root-zone") || "com";
}
Атрибута нет — подставляется com. На снятой странице он есть и равен com.
Атрибут data-tilda-root-zone читают 10 файлов из 53 — это самый
востребованный атрибут страницы. Общей функции для этого нет: почти каждый файл
несёт свою копию с той же логикой и своим именем.
| Файл | Имя функции |
|---|---|
tilda-scripts-3.0 | t_getRootZone |
tilda-forms-1.0 | t_forms__getRootZone |
tilda-delivery-1.0 | t_delivery__getRootZone |
tilda-lazyload-1.0 | t_lazyload__getRootZone |
tilda-zero-1.1 | t396__getRootZone |
tilda-wishlist-1.0 | twishlist__getRootZone |
tilda-phone-mask-1.1 | t_form_phonemask_getRootZone |
tilda-cart-1.1, tilda-catalog-1.1 | без имени, сжато до одной буквы |
tilda-buyer-dashboard-1.0 | читает атрибут прямо в t_bd__init |
Есть и обратная операция — для строк, где .com всё-таки зашит:
function t_modifyRootZone(t) {
return t && t.includes("static.tildacdn.com")
? t.replace("static.tildacdn.com", "static.tildacdn." + t_getRootZone())
: t;
}
То есть зашитые адреса в коде есть, и их правят на ходу подстановкой.
Карта поддоменов
Все найдены в 53 файлах рантайма. В колонке «как собирается» — то, что в коде
буквально. Считаю так: варианты одной и той же машины по зоне — одна строка
(store.tildaapi.com и store.tildaapi.ru это один адрес), а дублёр — отдельная,
потому что у него другое имя.
| Адрес | Как собирается | Задача |
|---|---|---|
static.tildacdn.<зона> | "https://static.tildacdn." + зона | JS, CSS, картинки |
optim.tildacdn.<зона> | "optim.tildacdn" + "." + зона | уменьшенные копии картинок |
static3.tildacdn.com | зашит, без зоны | подмена картинки при отказе static |
thumb.tildacdn.com | зашит | превью в lazyload-1.3.min.export.js |
neo.tildacdn.com | зашит, без зоны | спасательный код |
ws.tildacdn.com | зашит в HTML страницы | только dns-prefetch, см. ниже |
forms.tildacdn.com | window.tildaForm.endpoint | приём данных форм, брошенные корзины |
forms.tildaapi.<зона> | "forms.tildaapi." + зона | тот же приём, если зона задана |
forms2.tildacdn.com | зашит, подставляется при отказе | дублёр приёма форм |
store.tildaapi.<зона> | "store.tildaapi." + зона | товары, наличие, корзина каталога |
store2.tildaapi.<зона> | "store2.tildaapi." + зона | дублёр магазина при отказе |
delivery.tildacdn.com | "https://delivery" + endpointDelivery | список стран, расчёт доставки |
delivery2.tildacdn.com | endpointDelivery = "2.tildacdn.com" | дублёр доставки при отказе |
geoserv.tildacdn.com | "https://geoserv" + endpointDelivery | определение города, подсказки улиц |
geoserv2.tildacdn.com | тот же endpointDelivery | дублёр геосервиса |
geo.tildaapi.<зона> | "https://geo.tildaapi." + зона | страна посетителя для маски телефона |
members2.tildacdn.com | зашит, если зоны нет | кабинет покупателя |
members.tildaapi.<зона> | "https://members.tildaapi." + зона | тот же кабинет, если зона есть |
Отдельно — tildacdn.info: это не адрес, а второй домен верхнего уровня, который
избранное принимает наравне с .com.
Два адреса взяты не из кода, а из HTML снятой страницы: neo и ws. И ws
интересен тем, что его нет ни в одном из 53 файлов рантайма — в разметке для него
стоит <link rel="dns-prefetch">, то есть браузеру велено заранее выяснить адрес
машины, к которой на этой странице никто не обращается. static в HTML упомянут
49 раз, optim и store — ни разу: эти адреса появляются только в браузере,
когда отработают скрипты.
Два места стоит прочитать внимательно. Кабинет покупателя при наличии зоны уходит
не на другой поддомен, а на другой домен — tildaapi, а не tildacdn:
window.tildaMembers.endpoint = "https://members2.tildacdn.com";
var e = document.getElementById("allrecords");
if (e) { var t = e.getAttribute("data-tilda-root-zone");
t && (window.tildaMembers.endpoint = "https://members." + "tildaapi." + t) }
А избранное принимает адрес картинки только с двух доменов и молча отбрасывает любой другой:
"image" == p[0] && (p[1].indexOf("tildacdn.com") > 0 || p[1].indexOf("tildacdn.info") > 0) && (i = p[1])
Отсюда практический вывод: перенесли картинки товаров на свой домен — в избранном у них пропадут превью, и ошибки в консоли не будет.
Что именно лежит на static
Два вида путей, оба видны в коде и на странице.
Код — /js/<имя>.min.js и /css/<имя>.min.css. Именно так рантайм
догружает то, чего на странице изначально нет:
i.src = "https://static.tildacdn." + o + "/js/" + n + ".min.js"
s.href = "https://static.tildacdn." + o + "/css/" + n + ".min.css"
Картинки — путь начинается с /tild и шестнадцатеричных групп:
/tild6136-3837-4939-a634-366663326334/. На снятой странице таких путей семь, и
все с static.tildacdn.com. Плюс отдельная папка /lib/linea/ — оттуда корзина
берёт иконки «плюс», «минус» и «удалить».
Отложенная загрузка переписывает адрес не любой картинки, а только своей:
if (-1 === e.indexOf("static.tildacdn." + t_store__getRootZone())) return e;
Картинка с чужого домена в этот механизм не попадает вообще. А та, что попала, переезжает на домен оптимизатора — прямо в браузере, подменой в пути:
r = /\/static\.tildacdn\.(info|.{1,3})\//
function b(e) { return e.replace(r, function (e, t) {
return "/" + t_lazyload__getThumbDomainName() + "." + (l[t] || "com") + "/" }) }
function t_lazyload__getThumbDomainName() { return "optim.tildacdn" }
Поэтому optim не найти в исходном коде страницы: в разметке стоит static, а
optim появляется в адресе только после того, как отработал скрипт. Обратите
внимание и на саму регулярку: рядом с зоной из трёх знаков в ней стоит info —
это второй домен верхнего уровня, а не опечатка.
Если картинка с static не загрузилась, адрес меняется ещё раз, уже на зашитый
.com:
if (h(t)) return window.lazy_err_static = "y",
void L(e, t.replace(r, "/static3.tildacdn.com/"));
Формы: один повтор, и больше никогда
Адрес приёма формы лежит в window.tildaForm.endpoint и по умолчанию равен
forms.tildacdn.com. При неудаче код пробует дублёра:
if (secondFormTypeXhr && (secondFormTypeXhr.status >= 500 || 408 == secondFormTypeXhr.status
|| 410 == secondFormTypeXhr.status || 429 == secondFormTypeXhr.status
|| "timeout" == secondFormTypeXhr.statusText)
&& -1 !== window.tildaForm.endpoint.indexOf("forms.tilda"))
return window.tildaForm.endpoint = "forms2.tildacdn.com",
window.tildaForm.send(form, btnSubmit, formType, formKey), !1;
Условие переключения требует, чтобы в текущем адресе была подстрока
forms.tilda. После переключения адрес — forms2.tildacdn.com, и такой
подстроки в нём нет: там forms2.tilda. Значит:
- повтор возможен ровно один раз за жизнь страницы;
window.tildaForm.endpointпосле переключения остаётсяforms2до перезагрузки — все следующие отправки на этой странице идут туда и повтора уже не получают;- список поводов для повтора закрытый: 500 и выше, 408, 410, 429, таймаут.
Код
402или403в него не входит и повтора не вызывает.
Оттуда же корзина тянет брошенные заказы, и этот адрес зашит целиком, без зоны:
var e = "https://forms.tildacdn.com/api/v1/getlostbasket/";
Доставка: та же конструкция, тот же один повтор
Доставка держит не имя хоста, а его хвост:
endpointDelivery: ".tildacdn.com",
changeEndpoint: function (t, r, n) {
var i = this;
"object" == typeof t && (t.status >= 500 || 408 == t.status || 410 == t.status
|| 429 == t.status || t.isTimeout) && ".tildacdn.com" === i.endpointDelivery
? (i.endpointDelivery = "2.tildacdn.com", "function" == typeof r && r())
: …
Адреса собираются приклеиванием: "https://delivery" + endpointDelivery и
"https://geoserv" + endpointDelivery. После переключения хвост становится
2.tildacdn.com, и те же строки дают delivery2.tildacdn.com и
geoserv2.tildacdn.com.
Дальше — то же самое, что у форм: сторож ".tildacdn.com" === endpointDelivery
после переключения ложен навсегда. Один повтор, и всё. Список поводов совпадает
с формами вплоть до кодов.
Если и дублёр не ответил, посетитель ничего не узнаёт — сообщение уходит в консоль, по-английски:
console.error("[" + t.status + "] " + t.responseText + ". Please, try again later.")
Но зону доставка передаёт не в адресе, а внутри запроса. В четырёх местах файла стоит одно и то же:
var v = document.getElementById("allrecords").getAttribute("data-tilda-root-zone");
v && (l.rz = v);
Поэтому endpointDelivery и зашит на .tildacdn.com: адрес у доставки один на
все зоны, а зона едет полем rz в теле. Это единственная служба, устроенная так,
— у остальных зона попадает в имя хоста.
Магазин: те же адреса, но собранные фабрикой
Каталог держит адрес в window.t_store_endpoint и пересобирает его отдельной
функцией:
function t_store__setupEndpoint() {
var t = t_store__getRootZone();
window.t_store_endpoint = "store.tildaapi." + t;
}
До первого вызова там лежит зашитое store.tildaapi.com. А в корзине то же имя
собирает фабрика из двух частей, и вторая часть — выбор домена:
TCartServerLocation = { CDN: "tildacdn", API: "tildaapi" };
function t_cart__getServerName(t) { return "store." + t + "." + getRootZone() }
function t_cart__getServer2Name(t) { return "store2." + t + "." + getRootZone() }
То есть фабрика умеет собрать и store.tildacdn.<зона>. Но все шесть мест, где
её вызывают, передают TCartServerLocation.API — член CDN объявлен и не
используется ни разу. В таблицу я его не внёс именно поэтому: адрес, который
никто не запрашивает, — не адрес.
Разница с формами и доставкой в том, что магазин зону перечитывает: дублёр
получается store2.tildaapi.<зона>, а не зашитый .com. У форм наоборот —
переключение ведёт на forms2.tildacdn.com независимо от зоны. Подробнее, с
замером числа запросов на каждый код ответа, — в разборе
ошибок 402, 403 и 429.
Если домен недоступен
Первым на странице стоит tilda-fallback-1.0 — 1 918 байт, и это весь файл. Он
делает три вещи.
Ловит отказы. Обходит все теги LINK, SCRIPT и IMG, вешает каждому
onerror, а картинкам ещё и проверку «загрузилась, но ширина ноль».
Тянет спасательный код с другого домена. Не с того, который только что не ответил:
t.open("GET", "https://neo.tildacdn.com/js/tilda-fallback-advanced-1.0.min.js", !0)
Здесь .com зашит буквально — подстановки зоны в этом месте нет, в отличие от
всех адресов static.
Показывает страницу силой. Два таймера в конце файла:
setTimeout(t_fallback__handleTimeout, 3e4);
setTimeout(function () {
var t = document.getElementById("allrecords");
if (t && !t.classList.contains("t-records_animated")) {
var a = document.createElement("style");
a.innerHTML = "div.t-records {opacity: 1;}";
document.getElementsByTagName("head")[0].appendChild(a)
}
}, 5e3);
Через 5 секунд, если у #allrecords не появился класс t-records_animated,
в <head> вставляется правило opacity: 1. Отсюда знакомая картина: сайт на
Тильде, который «долго думал и потом резко появился целиком». Это не загрузка
закончилась — это сработал таймер. Через 30 секунд отдельный сторож ещё раз
пытается вытянуть спасательный файл, если документ всё ещё грузится.
Одна деталь без смягчений: t_fallback__reloadSRC при отсутствии продвинутого
файла зовёт сам себя через 500 мс — без счётчика попыток и без предела по
времени. Пока neo.tildacdn.com недоступен, цикл идёт всё время, что открыта
страница.
Девять атрибутов на теге #allrecords
Зона — не единственное, что рантайм читает из разметки. На снятой странице тег
несёт десять атрибутов data-tilda-*; вот они и число файлов рантайма, которые
их читают.
| Атрибут | Читают файлов | Что значит |
|---|---|---|
data-tilda-root-zone | 10 | зона в адресах CDN |
data-tilda-lazy | 9 | отложенная загрузка включена |
data-tilda-project-id | 6 | номер проекта |
data-tilda-formskey | 2 | ключ, с которым уходят формы |
data-tilda-page-id | 1 | номер страницы |
data-tilda-project-country | 1 | страна проекта, для маски телефона |
data-tilda-cookie-type | 0 | — |
data-tilda-page-alias | 0 | — |
data-tilda-project-headcode | 0 | — |
data-tilda-stat-scroll | 0 | — |
Четыре нижних не читает ни один из 53 файлов. Оговорка та же, что и всегда:
ноль означает «нет в общих файлах рантайма». Код блоков конкретной страницы
приезжает своим файлом tilda-blocks-page…, и читать их может он.
Что здесь практически важно: data-tilda-formskey и data-tilda-project-id
лежат в открытой разметке любой опубликованной страницы. Это не пароли, но по
ним страница и опознаётся на стороне Тильды — и они уезжают вместе с каждой
формой.
Как посмотреть у себя
// зона, из которой собираются все адреса CDN
document.getElementById('allrecords').dataset.tildaRootZone || 'com'
// куда эта страница отправит форму
window.tildaForm && window.tildaForm.endpoint
// сколько файлов и байт приехало с CDN Тильды
performance.getEntriesByType('resource')
.filter(r => /tildacdn/.test(r.name))
.reduce((a, r) => (a.n++, a.net += r.transferSize, a), { n: 0, net: 0 })
Если вторая команда вернула forms2.tildacdn.com, значит на этой странице уже
была неудачная отправка и повтор израсходован: следующая ошибка повтора не
получит.
Что из этого следует
Обращения к tildacdn для сайта на Тильде не побочные, а несущие: разбор того,
что именно оттуда приезжает и сколько это весит, — в «Возможности
Тильды». Правило одного повтора значит, что на
отправку формы полагаться как на гарантию нельзя: что показать после отправки
формы — про то, как это
выглядит для посетителя. А если данные из формы должны куда-то попасть помимо
Тильды, это уже дописывание.
Отдельный вопрос — что из этой карты остаётся, если сайт с Тильды уносить: файлы скачиваются, а адреса служб зашиты в код. Сколько именно файлов и какие службы не уедут — в «Переносе сайта с Тильды».