0% прочитано

Laravel Open Graph + Inertia SSR: чому LinkedIn показував прев'ю інакше, ніж Telegram

У ICanUp Telegram уже показував обкладинку, заголовок і опис Post, але LinkedIn формував картку некоректно. Я перевірила Open Graph у початковому HTML, відповідь для LinkedInBot, URL і параметри зображення, дублікати meta tags та кеш платформи. Цей кейс показав, чому робота прев'ю в одному месенджері ще не доводить, що social metadata налаштовані правильно.

11 вересня 2026 р. 9 хв читанняInertia.js

На сторінках Post у ICanUp Open Graph уже працював. Коли я надсилала посилання в Telegram, картка містила заголовок, опис і обкладинку.

Але LinkedIn поводився інакше: прев'ю не формувалося стабільно або виглядало некоректно.

Саме тоді стало зрозуміло, що коректна картка в одному месенджері ще не доводить, що Open Graph налаштований повністю.

Я не починала реалізацію Open Graph заново. Базові метатеги вже існували і рендерилися через Inertia SSR. Завданням був аудит усього контракту, який отримує crawler.
Laravel Open Graph metadata rendered with Inertia SSR and checked by Telegram and LinkedIn crawlers
Telegram уже показував картку Post, але LinkedIn змусив перевірити весь ланцюжок: початковий HTML, Open Graph, доступність зображення, його параметри та кеш платформи.

Що вже працювало до початку перевірки

Важливо було не переписати те, що вже працює.

Сторінка Post уже мала основні Open Graph і Twitter Card metadata, а Inertia SSR додавав їх у початковий HTML.

  • og:title
  • og:description
  • og:type
  • og:url
  • og:site_name
  • og:locale
  • og:image
  • og:image:width
  • og: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, який сервер віддає одразу.

BASH - Перевірити Open Graph у початковому 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 мають описувати саме ту публічну сторінку, яку зараз відкрито.

HTML - Спрощений Open Graph contract
<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, браузерна перевірка цього не покаже.

BASH - Порівняти HTML для браузера і LinkedInBot
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.html
Для мене правильний результат - social metadata не залежать від того, чи сторінку читає звичайний User-Agent, чи LinkedInBot.

og:image - це не просто URL

Наявність og:image ще не означає, що crawler зможе використати файл.

Social image є окремим HTTP-ресурсом. Він повинен бути публічним, доступним через HTTPS і повертати коректну відповідь без авторизації.

Я почала перевіряти зображення як частину API-контракту для crawler, а не просто як картинку в браузері.

BASH - Перевірити HTTP-відповідь social image
curl -I \  https://icanup.com.ua/path/to/social-image.png
Очікувана відповідь зображення
Expected: 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.

HTML - Повний набір параметрів social image
<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.

HTML - Alt metadata для 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.

Cover і fallback мають однаковий контракт
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 metadata

Inertia 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 повторно прочитати сторінку.

Старе прев'ю після deploy не завжди означає, що production HTML досі неправильний. Спочатку перевіряю curl, а вже потім cache конкретної платформи.

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, який має все це прочитати.