Задача: Настроить Open Graph / meta-теги для корректных превью страниц в мессенджерах
Настроить Open Graph / meta-теги для корректных превью страниц в мессенджерах
Явно задать для страниц сайта метаданные превью (заголовок, описание, изображение), чтобы мессенджеры и соцсети корректно формировали карточку ссылки.
Что нужно сделать
Добавить на страницы сайта явные метаданные для формирования превью ссылок в мессенджерах и соцсетях. В первую очередь — Open Graph (og:title, og:description, og:image, og:url, og:type), а при необходимости также Twitter Cards.
Сейчас картинка для превью явно не задана. Поэтому Telegram, WhatsApp, VK, Facebook и другие сервисы при отправке ссылки сами пытаются понять, какой заголовок, описание и изображение использовать. Они могут брать <title>, meta description, первое подходящее изображение со страницы, крупную картинку из контента или вообще сохранить ранее найденный вариант. В итоге превью может выглядеть непредсказуемо и отличаться от того, что мы хотим показать пользователю.
Почему это важно
Превью ссылки — это фактически мини-сниппет страницы внутри мессенджера. Оно влияет на то, как пользователь воспринимает ссылку до перехода: видит ли он понятный заголовок, релевантное описание и нормальную картинку товара/категории вместо случайного изображения.
Это не прямой фактор SEO-ранжирования, но относится к корректной разметке страницы и влияет на CTR, узнаваемость и качество расшаривания ссылок. Для поисковых систем и внешних сервисов также лучше, когда ключевые метаданные страницы заданы явно, а не определяются эвристически.
Что должно быть в разметке
Для каждой индексируемой страницы желательно формировать минимум:
<meta property="og:title" content="Название страницы">
<meta property="og:description" content="Краткое описание страницы">
<meta property="og:image" content="https://happybaby2000.ru/path/to/image.jpg">
<meta property="og:url" content="https://happybaby2000.ru/current-page/">
<meta property="og:type" content="website">
Для карточек товаров можно использовать og:type=product, если это соответствует текущей реализации и не создаёт проблем с поддержкой.
og:image должен содержать абсолютный публично доступный URL изображения. Для товаров желательно использовать основное фото товара, для категорий и информационных страниц — заранее определённое релевантное изображение или общий fallback.
Также проверить, чтобы изображение было доступно без авторизации, не блокировалось robots/firewall/CDN и корректно отдавалось внешним ботам.
Логика выбора изображения
Нужно определить понятный приоритет:
- Карточка товара — основное изображение товара.
- Категория — изображение категории, если оно задано.
- Информационная страница — отдельное изображение страницы, если задано.
- Если подходящего изображения нет — использовать общий fallback сайта.
Важно именно явно выводить og:image, а не рассчитывать на то, что мессенджер сам найдёт «правильную» картинку в HTML.
Как мессенджеры формируют превью
Когда пользователь впервые отправляет ссылку, сервер мессенджера обычно сам обращается к странице, считывает HTML и сохраняет найденные метаданные у себя. То есть превью часто формируется не на телефоне пользователя, а на стороне Telegram/WhatsApp/VK/другого сервиса.
Если Open Graph-разметка есть, сервис обычно ориентируется в первую очередь на неё. Если её нет или часть полей отсутствует, сервис использует собственные правила: может взять обычный <title>, description, первую крупную картинку, изображение из контента и т.д. Эти правила у разных сервисов отличаются, поэтому без явной разметки результат нельзя считать стабильным.
Отдельно про кэширование превью
Нужно учитывать, что мессенджеры кэшируют результат разбора URL. Например, если сегодня ссылка https://happybaby2000.ru/catalog/example/ уже была отправлена и мессенджер сохранил для неё старую картинку, то после замены картинки на сайте этот же URL некоторое время может продолжать показывать старое превью.
Причина в том, что мессенджер не обязан повторно скачивать страницу и изображение при каждой отправке ссылки. Он видит уже знакомый URL и использует сохранённый результат из своего кэша. Срок жизни такого кэша зависит от конкретного сервиса и нами обычно не контролируется.
Поэтому после изменения og:image нельзя проверять результат только повторной отправкой того же самого URL и делать вывод, что разметка не работает.
Как проверить новое превью, обходя старый кэш
Для проверки можно добавить к URL произвольный GET-параметр, например:
https://happybaby2000.ru/catalog/example/?preview=2
или:
https://happybaby2000.ru/catalog/example/?v=20260916
Для браузера и сайта это всё та же страница, если эти параметры не используются в логике страницы. Но для мессенджера это уже другой URL, которого ещё нет в его кэше. Поэтому он с большей вероятностью заново запросит страницу и сформирует новое превью по актуальным Open Graph-тегам.
Это не «очищает» старый кэш мессенджера. Просто новый URL с другим query string создаёт отдельную запись кэша и позволяет проверить актуальную разметку, не дожидаясь истечения старого кэша.
При тестировании желательно менять параметр каждый раз, когда нужно гарантированно проверить новый вариант, например ?preview=1, затем ?preview=2.
Критерии готовности
- На основных типах страниц явно выводятся Open Graph-теги.
og:title,og:descriptionиog:imageсоответствуют содержимому конкретной страницы.- У
og:imageиспользуется абсолютный URL. - Для страниц без собственного изображения предусмотрен fallback.
- Проверено, что внешние боты могут получить изображение.
- На нескольких типах страниц вручную проверено формирование превью в популярных мессенджерах.
- При проверке изменений учтён кэш: новый вариант дополнительно тестируется через URL с уникальным GET-параметром.
Ворклоги
Усилена проверка og:image после ошибок Google
Google сообщил для части карточек ошибку «Недопустимый URL в поле image». Предполагаемая причина — исходные пути к изображениям содержали незакодированные кириллические символы и пробелы, которые прежняя проверка не выявляла.
Доработан скрипт agent/scripts/check-page-image/index.ts проекта happybaby2000.ru:
- проверяется исходное значение
og:imageдо какой-либо нормализации; - URL должен быть абсолютным HTTP(S)-адресом с хостом;
- выявляются незакодированные пробелы, кириллица, управляющие и другие недопустимые символы;
- проверяется корректность
%-escape-последовательностей; - выявляются незакодированные квадратные скобки в path/query/fragment и повторный
#во фрагменте, при этом IPv6 в квадратных скобках допускается; - проверяются все
og:imageна странице, а не только первый; - диагностика указывает номер изображения, исходный URL и причину ошибки; для недопустимых символов также выводятся Unicode-код и позиция;
- при ошибках скрипт возвращает код
1, при успехе —0.
Рядом добавлен постоянный тест agent/scripts/check-page-image/index.test.ts на Vitest. Конфигурация Vitest расширена на scripts/**/*.test.ts. Набор покрывает 28 сценариев, включая валидные и невалидные URL, encoded-символы, IPv6, отсутствие изображения, несколько og:image и точность диагностики.
Проверка проходит успешно: 28 тестов пройдены, ESLint, Prettier и git diff --check — без ошибок.
Команда теста:
npm run test -- scripts/check-page-image/index.test.ts
Ограничение проверки: она валидирует синтаксис URL из og:image, но не проверяет доступность самого изображения и не гарантирует принятие URL сервисами Google. Автоматического исправления URL скрипт не выполняет.
Прогресс: сначала исполнимая проверка, потом реализация
Перед внесением изменений сначала был написан отдельный терминальный TypeScript-скрипт, который делает HTTP-запрос к странице, парсит HTML через cheerio и проверяет обязательные Open Graph-теги. Это дало объективный baseline до начала правок и готовый критерий приёмки для ИИ-агента.
Проверочный скрипт
import * as cheerio from "cheerio";
const REQUIRED_TAGS = [
"og:title",
"og:description",
"og:image",
"og:url",
"og:type",
] as const;
function isAbsoluteHttpUrl(value: string): boolean {
try {
const url = new URL(value);
return url.protocol === "http:" || url.protocol === "https:";
} catch {
return false;
}
}
async function main() {
const targetUrl = process.argv[2];
if (!targetUrl) {
console.error("Usage: npx tsx check-open-graph.ts <url>");
process.exit(2);
}
const response = await fetch(targetUrl, {
redirect: "follow",
headers: {
"User-Agent": "OpenGraphChecker/1.0",
},
});
if (!response.ok) {
console.error(`HTTP ${response.status} ${response.statusText}`);
process.exit(1);
}
const html = await response.text();
const $ = cheerio.load(html);
let hasErrors = false;
console.log(`HTTP: ${response.status}\n`);
const values = Object.fromEntries(
REQUIRED_TAGS.map((property) => {
const value =
$(`meta[property="${property}"]`).first().attr("content")?.trim() ?? "";
if (!value) {
console.log(`✗ ${property}: missing`);
hasErrors = true;
} else {
console.log(`✓ ${property}: ${value}`);
}
return [property, value];
}),
);
const image = values["og:image"];
const ogUrl = values["og:url"];
if (image && !isAbsoluteHttpUrl(image)) {
console.log(`✗ og:image is not absolute: ${image}`);
hasErrors = true;
}
if (ogUrl && !isAbsoluteHttpUrl(ogUrl)) {
console.log(`✗ og:url is not absolute: ${ogUrl}`);
hasErrors = true;
}
console.log(
hasErrors
? "\nRESULT: invalid or incomplete"
: "\nRESULT: OK",
);
process.exit(hasErrors ? 1 : 0);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
Состояние до реализации
Первый запуск дал ожидаемо отрицательный результат:
HTTP: 200
✗ og:title: missing
✗ og:description: missing
✗ og:image: missing
✗ og:url: missing
✗ og:type: missing
RESULT: invalid or incomplete
После этого задача была передана ИИ-агенту уже не в абстрактной формулировке «добавить Open Graph», а вместе с конкретным скриптом проверки и воспроизводимым способом проверить результат.
Отчёт агента
Агент сообщил о следующих изменениях:
Готово. Изменения:
index.tsx — добавлены props image и ogType, выводятся og:title, og:description, og:image, og:url, og:type
index.tsx — вычисляется imageUrl и передаётся в SeoHeaders с ogType="product"
Можешь проверить скриптом:
npx tsx scripts/check-page-image/index.ts "http://localhost:3000/catalog/product.html"
Повторная проверка
После изменений тот же проверочный подход дал:
HTTP: 200
✓ og:title: Мелкогабаритная площадка Корабль
✓ og:description: Размер 3.48 х 1.78 х 2.32 м.
✓ og:image: http://localhost:3000/images/resized/middle/images/img.jpg
✓ og:url: http://localhost:3000/catalog/product.html
✓ og:type: product
RESULT: OK
Практический вывод
Подход оказался полезным: сначала создаётся маленькая исполнимая проверка, фиксирующая текущее состояние и критерий готовности, а затем эта же проверка передаётся исполнителю вместе с задачей. Это уменьшает неоднозначность постановки и делает результат проверяемым не по отчёту агента, а по фактическому поведению системы.
Следующий шаг — усиливать этот шаблон: если агенту даётся проверочный скрипт, в постановке сразу требовать не только внести изменения, но и самостоятельно запустить этот скрипт после реализации и приложить фактический результат. Тогда цикл становится замкнутым: reproduce → implement → verify, а человеку остаётся уже контрольная, а не первичная проверка.