DEV Community

Julien Rousseau
Julien Rousseau

Posted on

Why your OpenGraph tags break on LinkedIn (and how to actually fix it)

Last week someone asked me why their React app showed an empty card when shared on LinkedIn, even though their tags were right there in the code.

The reason is simple: social scrapers don’t execute client-side JavaScript.

Twitterbot, LinkedInBot, and Slackbot aren't full browsers. They run a quick GET request, look at the raw HTML coming from your server, grab whatever is inside <head>, and close the connection.

If you use pure React or Vite without SSR, the bot only gets this:

<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/src/main.jsx"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The tags are injected after client hydration. By that time, the crawler is already gone.

How to fix it

First, your meta tags need to be in the initial HTML (using Next.js, Remix, Astro, or static prerendering).

Second, you need the actual dynamic image. Doing Figma exports for every blog post or product page gets old fast, and maintaining a headless Puppeteer instance on your backend to screenshot HTML is heavy and slow.

To solve this for my own projects, I built ogcraft.dev, focusing on two things:

A raw HTML visualizer: A free tool that scrapes strictly the raw initial HTML (no client-side JS) so you can preview exactly what Twitter and LinkedIn see before sharing.

A crawl-back image engine: Instead of passing huge encoded payloads or spinning up browser instances, you just drop a single tag on your page:

HTML

<meta property="og:image" content="https://api.ogcraft.dev/api/v1/extract?template=modern-blog&url=https://mysite.com/blog/my-post" />
Enter fullscreen mode Exit fullscreen mode

The API visits your page, extracts your existing tags (<title>, description, author), and renders an optimized dynamic card on the fly with CDN caching.

How do you guys usually handle dynamic OG images? Do you spin up Puppeteer/Satori in serverless functions, or use dedicated image APIs?

Top comments (0)