Generate Dynamic OG Images for Gatsby Without gatsby-plugin-image Overhead
Gatsby's image pipeline is powerful for optimizing photos and illustrations on your pages. But for OG images—the social preview cards that appear on Twitter, LinkedIn, Slack, and Facebook—you don't need gatsby-plugin-image, gatsby-plugin-sharp, or a headless browser running during your build. A single API URL does the job with zero build overhead.
Gatsby's Image Pipeline: Powerful but Heavy
Gatsby ships with a sophisticated image processing stack. gatsby-plugin-image and gatsby-plugin-sharp handle responsive images, lazy loading, blur-up placeholders, and WebP conversion. For content images on your pages, this pipeline is excellent.
But OG images are a fundamentally different problem. They are never rendered in a visitor's browser. They exist solely for social platform crawlers—bots that follow the URL in your og:image meta tag and fetch the image to display as a preview card. Your site visitors never see them directly.
Using Gatsby's image pipeline (or worse, spinning up Puppeteer at build time) to generate these images introduces real costs:
- Build time inflation. Each Puppeteer-rendered image adds seconds. Fifty blog posts means minutes added to every deploy.
- CI complexity. Headless Chrome needs specific system dependencies. Builds that work locally break in CI environments that lack the right libraries.
- Fragile upgrades. Puppeteer, Sharp, and Gatsby's image plugins have tightly coupled version requirements. Upgrading one frequently breaks another.
- One more thing to maintain. You end up writing and debugging a React component that only exists to be screenshotted by a headless browser. It's code that serves no user.
There is a simpler approach: point your og:image meta tag at an API that generates the image on demand.
How OGPeek Works
OGPeek is a REST API that returns a 1200×630 PNG image based on URL parameters. You pass a title, subtitle, template, theme, and brand color. The API renders the image and returns it. Social crawlers fetch this URL and display the result as your link preview.
The URL structure is straightforward:
https://ogpeek.com/api/v1/og?title=Your+Page+Title&subtitle=yourdomain.com&template=bold&theme=midnight&brandColor=%23FF7A00
No API key is needed for the free tier. No SDK to install. No build plugin to configure. You construct a URL, put it in a meta tag, and you're done.
Why this works for static sites: Social crawlers don't care where your OG image lives. They follow whatever URL is in your og:image tag. That URL can be a static file on your CDN or an API endpoint that generates the image dynamically. The crawler fetches it either way.
Step-by-Step: Adding OGPeek to gatsby-ssr.js
The most direct way to add OG images across your entire Gatsby site is through gatsby-ssr.js using the onRenderBody API. This runs during the build and injects meta tags into every page's <head>:
// gatsby-ssr.js
exports.onRenderBody = ({ setHtmlAttributes, setHeadComponents, pathname }) => {
const pageTitle = pathname === '/'
? 'Home'
: pathname.replace(/\//g, ' ').trim();
const ogImageURL = `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(pageTitle)
}&subtitle=${
encodeURIComponent('yourdomain.com')
}&template=bold&theme=midnight&brandColor=%23FF7A00`;
setHeadComponents([
<meta key="og:image" property="og:image" content={ogImageURL} />,
<meta key="og:image:width" property="og:image:width" content="1200" />,
<meta key="og:image:height" property="og:image:height" content="630" />,
<meta key="twitter:card" name="twitter:card" content="summary_large_image" />,
<meta key="twitter:image" name="twitter:image" content={ogImageURL} />,
]);
};
This gives every page on your Gatsby site a unique OG image based on the URL path. It's a starting point—for blog posts, you'll want the actual post title instead of the path. That's where the SEO component approach comes in.
SEO Component with OGPeek
Most Gatsby sites have a reusable SEO component. Here's how to integrate OGPeek into one that works with the modern Gatsby Head API:
// src/components/SEO.jsx
import React from 'react';
export function SEO({ title, description, pathname }) {
const siteUrl = 'https://yourdomain.com';
const ogImage = `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(title)
}&subtitle=${
encodeURIComponent('yourdomain.com')
}&template=bold&theme=midnight&brandColor=%23FF7A00`;
return (
<>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={`${siteUrl}${pathname}`} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImage} />
</>
);
}
Use it in any page or template by exporting a Head function:
// src/pages/about.jsx
import { SEO } from '../components/SEO';
export const Head = () => (
<SEO
title="About Us"
description="Learn about our team and mission."
pathname="/about/"
/>
);
export default function AboutPage() {
return <main>{/* page content */}</main>;
}
GraphQL Query Integration for Blog Posts
For blog posts created from Markdown or a CMS, you pull the title from Gatsby's GraphQL data layer. The OGPeek URL gets built from the query result:
// src/templates/blog-post.jsx
import React from 'react';
import { graphql } from 'gatsby';
import { SEO } from '../components/SEO';
export const Head = ({ data }) => {
const post = data.markdownRemark.frontmatter;
return (
<SEO
title={post.title}
description={post.description || post.excerpt}
pathname={post.slug}
/>
);
};
export const query = graphql`
query BlogPost($id: String!) {
markdownRemark(id: { eq: $id }) {
html
frontmatter {
title
description
slug
date(formatString: "MMMM DD, YYYY")
}
excerpt(pruneLength: 160)
}
}
`;
export default function BlogPostTemplate({ data }) {
const post = data.markdownRemark;
return (
<main>
<h1>{post.frontmatter.title}</h1>
<time>{post.frontmatter.date}</time>
<section>{/* render post.html here */}</section>
</main>
);
}
Every blog post now gets a unique OG image generated from its actual title. No images to manually create, no build step to maintain. The title flows from your Markdown frontmatter through GraphQL into the OGPeek URL.
Gatsby Head API vs. react-helmet
Gatsby introduced the Head API in Gatsby 4.19 as a replacement for gatsby-plugin-react-helmet. If your site still uses react-helmet, the OGPeek integration works the same way—just place the meta tags inside a <Helmet> component instead of the Head export:
// Legacy react-helmet approach
import { Helmet } from 'react-helmet';
function SEOHelmet({ title, description }) {
const ogImage = `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(title)
}&subtitle=${
encodeURIComponent('yourdomain.com')
}&template=bold&theme=midnight&brandColor=%23FF7A00`;
return (
<Helmet>
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImage} />
</Helmet>
);
}
The Head API is the recommended path going forward. It is faster (no runtime JavaScript), simpler (no plugin dependency), and supported in all current Gatsby versions. But if you're on an older codebase, react-helmet works identically for OG image purposes since the meta tags are rendered at build time either way.
Comparison: OGPeek API vs. Alternatives
There are several ways to generate OG images for a Gatsby site. Here's how they stack up:
| OGPeek API | gatsby-plugin-open-graph-images | Puppeteer in CI | |
|---|---|---|---|
| Setup | Add URL to meta tag | Install plugin + configure gatsby-node | Install Puppeteer + write render script |
| Build time impact | Zero | Seconds per page | Seconds per page |
| CI dependencies | None | Headless Chrome | Headless Chrome |
| Templates | 7 built-in, customizable | Write your own React component | Write your own HTML/CSS |
| Maintenance | None (hosted API) | Plugin version pinning | Puppeteer version pinning |
| Cost | Free tier: 50/day. Pro: $9/mo | Free (your build minutes) | Free (your build minutes) |
The build-time approaches give you full control over the image layout since you write the component yourself. The tradeoff is build complexity, CI fragility, and ongoing maintenance. For most Gatsby sites—blogs, documentation, marketing pages—the API approach is significantly simpler and the built-in templates cover the common use cases.
Performance note: OGPeek images are generated in under 500ms and cached at the edge. Social crawlers (which are the only consumers of OG images) handle this latency without issue. Your site visitors never make requests to OGPeek—the URL is only followed by bots.
Add OG images to your Gatsby site in 2 minutes
Open the playground, enter a blog post title, and preview the generated image. Copy the URL into your SEO component. No signup required for the free tier.
Open the playground →Wrapping Up
Gatsby's image pipeline solves a real problem for on-page images. But applying that same pipeline to OG images—images that only social crawlers ever see—adds build time, CI dependencies, and maintenance burden for no user-facing benefit.
An API-based approach decouples OG image generation from your Gatsby build entirely. You construct a URL with your page title and brand parameters, place it in a meta tag, and every page gets a unique social preview card. The integration is a few lines in an SEO component regardless of whether you use the Gatsby Head API or react-helmet.
Your Gatsby build stays fast. Your OG images stay dynamic.