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:
- Slack's bot fetches the URL with a User-Agent like
Slackbot-LinkExpanding 1.0. - It reads the raw HTML response text.
- It parses
<meta property="og:image" content="...">from the<head>. - It fetches that image URL and displays it in the link preview.
- 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 forsummary.
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:imagevalue 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.