OG Image Lazy Loading: Does og:image Support Lazy Load?

Social crawlers don't execute JavaScript — og:image must be a static absolute URL. Here's what 'lazy loading' means for OG images and how to serve them correctly.

Short answer: No, og:image does not support lazy loading

Lazy loading is a browser technique where images are deferred until they enter the viewport, typically via the loading="lazy" attribute on <img> tags or JavaScript-based Intersection Observer. This requires a browser to execute JavaScript and track DOM events.

Social crawlers — Twitterbot, Discordbot, LinkedInBot, Slackbot — do not execute JavaScript. They fetch your HTML, parse it as text, and extract the value of og:image directly from the <meta> tag in your <head>. Lazy loading is irrelevant — the crawler reads the URL from the attribute, not from the rendered DOM.

What crawlers actually do

When a platform like Slack unfurls a shared link, here's what happens:

  1. Slack's bot fetches the URL with a User-Agent like Slackbot-LinkExpanding 1.0.
  2. It reads the raw HTML response text.
  3. It parses <meta property="og:image" content="..."> from the <head>.
  4. It fetches that image URL and displays it in the link preview.
  5. No JavaScript is executed. No browser rendering happens. No DOM events fire.

This means your og:image must be a fully resolved, static, absolute URL present in the raw HTML the server sends.

The real problem: SPAs and dynamic OG tags

The closest equivalent to "lazy loading" breaking OG images is when developers add OG tags via JavaScript after the initial HTML is returned. In a Single Page Application, the page shell is served as near-empty HTML, and React/Vue/Angular fills in the content (including meta tags) client-side. Crawlers never see these tags.

<!-- What the crawler sees (SPA shell): -->
<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
    <!-- NO og:image — it's injected by JS later -->
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>

<!-- What you want the crawler to see: -->
<!DOCTYPE html>
<html>
  <head>
    <title>My Post Title</title>
    <meta property="og:image" content="https://yourdomain.com/og/my-post.png" />
    <meta property="og:title" content="My Post Title" />
    <meta property="og:description" content="Post excerpt here" />
  </head>
  ...
</html>

How to correctly serve og:image

For OG tags to work, the og:image URL must be:

  • Absolute URL: Must include protocol and domain: https://yourdomain.com/og/image.png — not /og/image.png.
  • HTTPS: HTTP-only URLs are rejected by most platforms.
  • Publicly accessible: No authentication, no signed URLs that expire before the crawler fetches them.
  • Present in the initial HTML response: In the <head> of the HTML returned by your server — not injected by JavaScript.
  • Correct dimensions: 1200×630 px for summary_large_image; at least 200×200 px for summary.

Serving the image URL efficiently (on-demand generation)

Even though the URL must be static in the HTML, the image at that URL can be generated on-demand the first time it's fetched and then cached. This is what services like @vercel/og and Cloudinary do — they generate the image when the URL is first hit and cache it on the CDN for subsequent requests.

// The og:image URL is static in the HTML:
<meta property="og:image" content="https://yourdomain.com/og?title=My+Post" />

// But the image is generated on-demand at /og when first fetched:
// app/og/route.tsx (Next.js Edge Function)
import { ImageResponse } from 'next/og';
export const runtime = 'edge';

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const title = searchParams.get('title') ?? 'Untitled';

  return new ImageResponse(
    (<div style={{ /* ... */ }}>{title}</div>),
    {
      width: 1200,
      height: 630,
      headers: {
        // Cache on CDN for 1 year after first generation:
        'Cache-Control': 'public, max-age=31536000, immutable',
      },
    }
  );
}

What about lazy loading the og:image in the HTML body?

You might have an <img> tag in your page body that displays the same OG image with loading="lazy". This is completely fine — lazy loading the visible image for users does not affect the OG meta tag, which is separate in the <head>. The crawler reads the <meta> tag, not the<img> tag.

<head>
  <!-- This is what crawlers read: -->
  <meta property="og:image" content="https://yourdomain.com/og/my-post.png" />
</head>
<body>
  <!-- This is completely independent — lazy loading here is fine: -->
  <img src="https://yourdomain.com/og/my-post.png"
       loading="lazy"
       alt="Post cover"
       width="1200"
       height="630" />
</body>

Summary

  • OG tags are parsed from raw HTML — JavaScript never runs for crawlers.
  • og:image value must be in the HTML from the server, not injected later.
  • Lazy loading the image in the body (<img loading="lazy">) does not affect the OG meta tag.
  • The image itself can be generated on-demand (Satori, Puppeteer, Cloudinary) — just cache aggressively.

Test your OG tags free

Paste any URL into OGFixer to see exactly how your link previews look on Twitter, LinkedIn, Discord, and Slack.

Related Guides