На сторінках Post у ICanUp Open Graph уже працював. Коли я надсилала посилання в Telegram, картка містила заголовок, опис і обкладинку.
Але LinkedIn поводився інакше: прев'ю не формувалося стабільно або виглядало некоректно.
Саме тоді стало зрозуміло, що коректна картка в одному месенджері ще не доводить, що Open Graph налаштований повністю.

Що вже працювало до початку перевірки
Важливо було не переписати те, що вже працює.
Сторінка Post уже мала основні Open Graph і Twitter Card metadata, а Inertia SSR додавав їх у початковий HTML.
og:titleog:descriptionog:typeog:urlog:site_nameog:localeog:imageog:image:widthog:image:height- Twitter Card metadata
- абсолютні URL для canonical і social image
- fallback-зображення для Post без обкладинки
- локалізовані UK та EN title і description
Telegram працював, але це не було достатньою перевіркою
Telegram уже міг побудувати картку з наявних metadata.
Це легко сприйняти як доказ того, що серверна частина завершена.
Але різні платформи мають власні crawler, правила перевірки та кеш. Тому одна платформа може прийняти неповний набір даних, а інша - показати слабке місце.
Моя перевірка мала відповідати не на питання "чи працює Telegram?", а на питання "чи є public metadata contract повним і стабільним?".
Спочатку я перевірила початковий HTML
Для crawler немає сенсу в meta tag, який з'являється тільки після виконання JavaScript.
Тому перша перевірка - не DevTools після hydration, а HTML, який сервер віддає одразу.
curl -s \ https://icanup.com.ua/en/posts/example-slug \ | grep -E 'og:|twitter:'Цей принцип у мене вже з'являвся раніше. Я окремо описувала випадок, коли meta tags були видимі в браузері, але відсутні в початковому HTML через Inertia SSR.
Для social preview ця перевірка так само важлива.
Open Graph для Post має описувати поточну публічну сторінку
У технічному сенсі в застосунку я працюю з Post. Значення article у og:type - це вже Open Graph vocabulary, а не назва моєї domain entity.
Для кожної мовної версії metadata мають описувати саме ту публічну сторінку, яку зараз відкрито.
<meta property="og:title" content="Localized Post title"><meta property="og:description" content="Localized description"><meta property="og:type" content="article"><meta property="og:url" content="https://example.com/en/posts/example-slug"><meta property="og:image" content="https://example.com/media/social-image.png">Потім я порівняла звичайний запит і LinkedInBot
Наступне питання було простим: чи отримує LinkedIn той самий серверний HTML, що й звичайний відвідувач?
Якщо middleware, security rule або інша серверна логіка поводиться по-різному для crawler, браузерна перевірка цього не покаже.
curl -s \ -A 'Mozilla/5.0' \ https://icanup.com.ua/en/posts/example-slug \ > /tmp/browser.html curl -s \ -A 'LinkedInBot' \ https://icanup.com.ua/en/posts/example-slug \ > /tmp/linkedin.html grep -E 'og:|twitter:' \ /tmp/browser.html grep -E 'og:|twitter:' \ /tmp/linkedin.htmlog:image - це не просто URL
Наявність og:image ще не означає, що crawler зможе використати файл.
Social image є окремим HTTP-ресурсом. Він повинен бути публічним, доступним через HTTPS і повертати коректну відповідь без авторизації.
Я почала перевіряти зображення як частину API-контракту для crawler, а не просто як картинку в браузері.
curl -I \ https://icanup.com.ua/path/to/social-image.pngExpected: HTTP 200Content-Type: image/png or HTTP 200Content-Type: image/jpeg- Публічний HTTPS URL.
- Відповідь
200 OK. - Без Basic Auth або іншої авторизації.
- Без помилки доступу.
- Без неочікуваного redirect.
- Коректний
Content-Type. - Формат JPG або PNG.
Розміри і MIME type мають описувати реальний файл
Ще один ризик - жорстко записати width, height або type, а потім замінити обкладинку іншим файлом.
Тоді HTML описує одне зображення, а сервер фактично віддає інше.
Metadata зображення мають походити з реального media file або з окремо підготовленого social preview.
<meta property="og:image" content="https://example.com/media/post-cover.png"> <meta property="og:image:width" content="1200"> <meta property="og:image:height" content="630"> <meta property="og:image:type" content="image/png">Розмір 1200 x 630 зручний, але значення не можна вигадувати
Для social preview зручно мати окреме зображення приблизно 1200 x 630, але сама цифра не повинна бути просто hardcoded promise.
Якщо реальний файл має інший розмір, Open Graph має або передати його фактичні параметри, або використовувати спеціально підготовлену social image.
Alt для social image теж став частиною contract
До аудиту легко зосередитися тільки на title, description і URL зображення.
Але повніший набір включає також опис social image.
<meta property="og:image:alt" content="Localized Post preview image"> <meta name="twitter:image:alt" content="Localized Post preview image">UK і EN не повинні ділити один текст metadata
У ICanUp одна сутність Post має локалізовані публічні версії.
Тому UK і EN сторінки повинні мати власні title, description, URL і locale metadata.
LinkedIn не повинен отримати EN URL з UK описом або навпаки.
Metadata | UK | EN |
|---|---|---|
og:title | Український title | English title |
og:description | Український description | English description |
og:url | /posts/shared-slug | /en/posts/shared-slug |
og:image:alt | Український alt | English alt |
Це продовжує той самий URL contract, який я використовую для canonical і hreflang на мультимовних сторінках Laravel.
Fallback image потрібне перевіряти окремо
Не кожен Post обов'язково має власну обкладинку.
Тому social metadata повинні мати безпечний fallback, який так само доступний crawler і має коректні параметри.
Fallback не повинен бути лише frontend placeholder, якого немає в початковому HTML.
Post has cover-> use Post social image Post has no cover-> use public fallback image Both paths:-> absolute HTTPS URL-> public 200 response-> correct image metadataInertia navigation додала ще один ризик - дублікати meta tags
SSR вирішує проблему початкового HTML, але після цього застосунок продовжує працювати через Inertia navigation.
Якщо metadata не мають стабільної identity у head manager, після переходів можна отримати дублікати або застарілі значення.
Social metadata мають бути унікальними як після SSR, так і після клієнтської навігації.
- Один
og:title. - Один
og:description. - Один
og:url. - Один актуальний
og:image. - Без старих meta tags від попереднього Post.
- Стабільні head keys для керування елементами.
Browser DevTools тут недостатньо
Після цього кейсу я перестала вважати один browser screenshot достатньою перевіркою social preview.
У мене з'явилося кілька окремих рівнів перевірки.
Рівень | Що перевіряю |
|---|---|
HTML | Open Graph і Twitter Card присутні до JavaScript |
Crawler | LinkedInBot отримує ті самі metadata |
Image | HTTPS, 200, Content-Type, dimensions |
Localization | UK і EN мають правильні значення |
Inertia | Meta tags не дублюються після navigation |
Platform | Результат через inspector/debugger |
Кеш платформи може приховувати вже виправлений результат
Навіть після виправлення HTML LinkedIn може певний час показувати стару картку.
Це важливий момент: social platform не обов'язково повторно читає URL після кожного поширення.
Тому після зміни metadata потрібно перевірити URL через platform inspector і змусити crawler повторно прочитати сторінку.
LinkedIn Post Inspector став частиною фінальної перевірки
- Перевірити URL через LinkedIn Post Inspector.
- Перевірити, яку картку бачить crawler після повторного fetch.
- Перевірити image URL.
- Окремо повторити Telegram.
- Перевірити WhatsApp або інший messenger.
- За потреби звірити результат через Meta Sharing Debugger.
Що я не змінювала
Цей аудит не вимагав міняти дизайн Post або перебудовувати SSR architecture.
Так само не було сенсу торкатися canonical чи hreflang, якщо вони вже формували правильні locale-aware URL.
Проблему потрібно було локалізувати в social preview contract, а не переписувати всю SEO infrastructure.
Open Graph і BlogPosting мають різні ролі
На сторінці Post одночасно можуть бути Open Graph metadata і JSON-LD.
Вони описують ту саму публічну сторінку, але мають різні завдання.
Open Graph насамперед використовується для preview під час поширення URL, тоді як Schema.org BlogPosting описує структуроване представлення Post.
Про другий контракт я окремо писала у матеріалі про Laravel JSON-LD BlogPosting, автора, canonical URL і локалізовані metadata.
Мій порядок перевірки social preview
- Відкрити raw HTML через
curl. - Переконатися, що Open Graph існує без виконання JavaScript.
- Звірити звичайний User-Agent і LinkedInBot.
- Перевірити абсолютний
og:url. - Перевірити абсолютний HTTPS
og:image. - Зробити окремий HEAD request до зображення.
- Звірити реальні width, height і MIME type.
- Перевірити
og:image:altіtwitter:image:alt. - Перевірити UK та EN окремо.
- Переконатися, що Inertia navigation не створює дублікати.
- Оновити кеш через platform inspector.
- Повторно перевірити реальне preview.
Чого я б не робила
- Не вважала б Telegram єдиним тестом Open Graph.
- Не перевіряла б metadata тільки через DevTools після hydration.
- Не hardcode width і height, якщо реальний файл може змінитися.
- Не залишала б
og:imageна URL, який потребує авторизації. - Не ігнорувала б MIME type зображення.
- Не змішувала б UK title з EN URL.
- Не залишала б дублікати meta tags після Inertia navigation.
- Не робила б висновок про production тільки зі старого кешованого preview.
Коли Telegram показує красиву картку, це хороший сигнал. Але тільки raw HTML, crawler response і реальний image contract показують, чи social metadata справді надійні.
Що я залишила собі на майбутнє
- Перевіряти social metadata у початковому HTML.
- Тестувати crawler User-Agent окремо.
- Вважати social image повноцінною частиною HTTP contract.
- Передавати реальні параметри media file.
- Локалізувати title, description, URL і alt.
- Мати публічний fallback для Post без обкладинки.
- Контролювати дублікати head metadata після Inertia navigation.
- Після deploy враховувати кеш конкретної платформи.
- Не переписувати всю SEO architecture через проблему одного preview.
Висновок
У цьому кейсі проблема була цікавою саме тому, що Open Graph не був повністю зламаний. Telegram уже показував нормальну картку Post.
LinkedIn змусив мене перевірити контракт глибше: початковий HTML, відповідь для LinkedInBot, URL social image, HTTP headers, фактичні параметри файла, локалізацію, дублікати в head і кеш самої платформи.
У ICanUp після цього social preview перестав бути для мене просто набором кількох og:* тегів.
Надійний preview - це узгоджений ланцюжок від Post і Inertia SSR до публічного image URL та crawler, який має все це прочитати.



