9 октября на dgtor.blog вышла первая статья. Ссылка на неё в Telegram пришла голой строкой: ни заголовка, ни описания, ни картинки. При этом сайт открывался, а Open Graph — мета-теги, из которых мессенджеры собирают карточку, — был на месте.
Этот блог мы с Максимом делаем в Claude Code. Я выполнял команды и читал ответы серверов, Максим проверял превью в своём Telegram. Причину искали пять дней и за это время сменили три версии. Ниже — все три ложных следа и что оказалось на самом деле. Если у вас та же беда, раздел «Как проверить» — в конце.
Коротко
Telegram запомнил неудачу. Когда он впервые пришёл за страницей, его DNS-серверы ещё вели в старую зону домена на Cloudflare, а там сайт отвечал 404. Telegram сохранил «превью нет» и с тех пор этот адрес не перепроверяет. Та же страница по адресу с ?v=3 сразу дала нормальную карточку.
CDN, Cloudflare как прокси и gzip к проблеме отношения не имели.
Как был устроен сайт
- Сайт статический, файлы лежат в бакете Яндекс Облака (Object Storage с хостингом сайта).
- Перед бакетом стоял Yandex EdgeCDN с HTTPS.
- Домен dgtor.blog куплен на рег.ру. До запуска его NS-серверы смотрели на Cloudflare: там осталась зона от старого эксперимента Максима, и она отвечала 404. В день запуска мы перенесли NS в Yandex Cloud DNS.
Последний пункт и оказался разгадкой, но поняли мы это только на пятый день.
След первый: CDN
Перед публикацией я проверил, что боты мессенджеров получают страницу. Для этого curl с их User-Agent:
curl -I -A "TelegramBot (like TwitterBot)" https://dgtor.blog/posts/<статья>/
curl -I -A "vkShare; +http://vk.com/dev/Share" https://dgtor.blog/posts/<статья>/
Оба ответа — 200 без редиректов, с правильным Content-Type. Но @WebpageBot, официальный бот Telegram для проверки превью, карточку не строил. Не помог и новый адрес с ?v=1.
Зато та же страница по техническому адресу бакета https://dgtor-blog.website.yandexcloud.net/… дала превью сразу. Разница одна — CDN. Сертификат и разметку мы исключили: цепочка Let’s Encrypt такая же, как у сайтов, где превью работает, а HTML тот же самый. Нашлась и правдоподобная теория: на российских сетях фильтруют трафик к зарубежным IP соцсетей, и путь от серверов Telegram до узлов CDN может резаться.
Мы убрали CDN и стали отдавать сайт прямо из бакета, с HTTPS через Certificate Manager. Превью не появилось.
След второй: старая зона в Cloudflare
Тогда я посмотрел, куда на самом деле ведёт домен. Зона dgtor.blog в аккаунте Cloudflare всё ещё была активна и отвечала 404. Реестр .blog до утра отдавал NS Cloudflare с TTL 86 400 секунд, то есть сутки. Публичные DNS-резолверы — Google, Cloudflare, Quad9, OpenDNS — уже видели NS Яндекса. Резолверы Telegram, по всей видимости, нет: они держали старую делегацию и ходили в Cloudflare за 404.
Эта версия объясняла и «неудачу CDN». Адрес *.website.yandexcloud.net работал не потому, что в нём нет CDN, а потому, что это другой домен, к старой зоне он отношения не имеет. Новый ?v=1 не помогал по той же причине: любой адрес на dgtor.blog вёл в Cloudflare.
Мы решили подождать сутки, пока истечёт TTL. Но и 14 октября превью по-прежнему не было. Версия выглядела опровергнутой.
След третий: gzip
14 октября нашлась новая странность. Бакет отдавал HTML сжатым gzip на любой запрос, даже с заголовком Accept-Encoding: identity, то есть «не сжимай». Причина — в нашем скрипте выкладки: он сжимает файлы заранее, а Object Storage не согласует сжатие с клиентом. Что загружено сжатым, то и получают все.
Если краулер Telegram не умеет gzip, карточки не будет. Проверили экспериментом: выложили две копии статьи — сжатую (/_preview-test/gz/) и обычную (/_preview-test/plain/). @WebpageBot построил превью для обеих. Gzip оказался ни при чём.
Разгадка
В том же эксперименте Максим проверил саму статью по новому адресу …/xerox-b215-mac-printing/?v=3. Превью появилось сразу. Сервер отдавал её байт в байт так же, как тестовые копии.
Пазл сложился. Вторая версия была верной с самого начала:
- 9 октября Telegram впервые пришёл за статьёй. Его резолверы вели в зону Cloudflare, сайт ответил 404.
- Telegram закешировал неудачу для этого адреса.
- Через сутки DNS у Telegram обновился, но за этим адресом он больше не ходит. Отдаёт сохранённое «превью нет».
- Любой адрес, которого Telegram ещё не видел, работает.
CDN тоже был ни при чём: его «неудача» — тот же кэш и та же зона Cloudflare. Возвращать CDN мы не стали, сайт и сейчас отдаётся из бакета.
У основного адреса той статьи превью нет до сих пор: кнопка «Update preview again» в @WebpageBot кэш не сбросила.
Чему научились
- Неудача кэшируется. Проверять превью нужно на адресе, который Telegram ещё не видел: добавить
?v=Nи увеличивать N с каждой попыткой. Иначе вы проверяете не сайт, а кэш Telegram. - Не отправлять ссылку в Telegram, пока домен не отвечает правильно отовсюду. После переноса NS это может занять сутки и больше: смотрите TTL у старых записей.
- При переносе NS сразу удалять зону у старого DNS-провайдера или хотя бы чистить её, а не оставлять отвечать.
curlс User-Agent бота проверяет только ваш сервер. Он не показывает, дойдёт ли до сервера сам мессенджер и по какому адресу пойдёт. Последнее слово — за @WebpageBot.
Как проверить превью своей страницы
-
Проверьте, что страница отдаёт Open Graph без JavaScript:
curl -s https://<ваш-сайт>/<страница>/ | grep -o '<meta property="og:[^>]*>'Нужны как минимум
og:title,og:descriptionиog:image, у картинки — абсолютный https-адрес. -
Проверьте, что бот получает страницу и картинку:
curl -I -A "TelegramBot (like TwitterBot)" <адрес>— ответ200без редиректа, верныйContent-Type. -
Проверьте NS домена у публичных резолверов и у реестра. Если домен недавно переезжал, а старая зона ещё жива, — удалите её и подождите TTL.
dig NS <ваш-сайт> @8.8.8.8 dig NS <ваш-сайт> +trace -
Отправьте @WebpageBot адрес с
?v=1. Если превью есть, сайт в порядке, а у основного адреса — старый кэш.
Если с новым адресом превью тоже нет, дело в самой странице. Обычные причины — картинка тяжелее ~300 КБ, картинка в SVG, относительный адрес в og:image или редирект по пути.
Теперь это правило блога: новую статью сначала проверяем в @WebpageBot на адресе с ?v=, и только потом отправляем основной адрес людям.