Skip to content

Commit ee867b8

Browse files
Merge pull request #80 from WebDevSimplified/dynamic-og-images
Add Dynamic OG Images Article
2 parents f255714 + 601e23c commit ee867b8

File tree

7 files changed

+295
-2
lines changed

7 files changed

+295
-2
lines changed
1.71 KB
Loading
10.5 KB
Loading
31.4 KB
Loading
9.21 KB
Loading
4.7 KB
Loading

src/components/BaseHead.astro

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export interface Props {
77
const { title, description, permalink } = Astro.props
88
const url = new URL(permalink)
99
const imageUrl = new URL("og.webp", url)
10-
imageUrl.protocol = "https:"
1110
---
1211

1312
<!-- Global Metadata -->
@@ -26,7 +25,11 @@ imageUrl.protocol = "https:"
2625
<meta property="og:title" content={title} />
2726
<meta property="og:description" content={description} />
2827
<meta property="og:image" content={imageUrl.toString()} />
29-
<meta property="og:image:secure_url" content={imageUrl.toString()} />
28+
{
29+
imageUrl.protocol === "https:" && (
30+
<meta property="og:image:secure_url" content={imageUrl.toString()} />
31+
)
32+
}
3033
<meta property="og:image:width" content="1200" />
3134
<meta property="og:image:height" content="630" />
3235

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
---
2+
layout: "@layouts/BlogPost.astro"
3+
title: "How to Create Dynamic Open Graph Images Automatically for Your Site"
4+
date: "2025-09-22"
5+
description: "Learn how to automatically generate dynamic Open Graph images for your website using JavaScript."
6+
tags: ["JavaScript", "Technical Discussion", "Node.js"]
7+
---
8+
9+
import Tangent from "@blogComponents/lib/Tangent.astro"
10+
11+
Open graph images are a crucial part of any site (especially a content heavy site like a blog or ecommerce site). They help improve the appearance of your links when shared on social media platforms, but if you are anything like me creating and managing hundreds of images is a massive pain. Fortunately, there is a better way to handle this: dynamic Open Graph images.
12+
13+
In this article I will show you the exact process I used to create dynamic Open Graph images for this blog and the best part is it will work for any site (even if it isn't written in JavaScript).
14+
15+
<Tangent>
16+
If you want to see a live example of my open graph images you can just add
17+
`/og.webp` to the end of any blog post URL. For example, [this is the Open
18+
Graph image for this post](/2025-09/dynamic-og-images/og.webp).
19+
</Tangent>
20+
21+
## What are Open Graph Images?
22+
23+
Before we can dive into creating Open Graph images we first need to understand what they are and why they are important. Open Graph images are images that show up in a preview when a link to your site is shared on places like social media, Slack, Discord, and even some texting apps. Below is an example what sharing this link on Facebook looks like with an Open Graph image.
24+
25+
<div style="max-width: 500px; margin-inline: auto;">
26+
![Example of a link on Facebook with an Open Graph
27+
image](/articleAssets/2025-09/dynamic-og-images/og-image-facebook.webp)
28+
</div>
29+
If you site does not contain an Open Graph image then the preview will have no image
30+
at all and will look something like this:
31+
32+
<div style="max-width: 500px; margin-inline: auto;">
33+
![Example of a link on Facebook without an Open Graph
34+
image](/articleAssets/2025-09/dynamic-og-images/og-image-facebook-no-image.webp)
35+
</div>
36+
37+
As you can see having no image makes your link much less engaging and will result in significantly fewer clicks to your site.
38+
39+
## Creating Dynamic Open Graph Images
40+
41+
If you have read any other articles on how to create dynamic Open Graph images you have probably seen solutions that involve using a headless browser like Puppeteer to render a page and take a screenshot of it. This will work, but I find it is quite slow, doesn't work well with build tools, and is generally more complex than it needs to be. Instead I am going to show you a much simpler approach that you can run in any build process or even just run from the command line to automatically generate every Open Graph image for your site in a matter of milliseconds per image.
42+
43+
<Tangent>
44+
To put into perspective how quick this process is, I am able to generate all
45+
143 Open Graph images for this blog in 40 seconds using the free tier of
46+
Netlify as part of my build process. Each image takes around 250-300ms to
47+
generate. On my local machine I can generate all 143 images in under 25
48+
seconds.
49+
<br />
50+
If build times were a major concern for you and this still isn't fast enough
51+
you could set up your build tool to only generate images for new posts or
52+
posts that have been updated since the last build which would add only a few
53+
seconds to your build time.
54+
</Tangent>
55+
56+
The way I am able to create such fast image generation is because we never actually render a full web page. Instead we use a library called [Satori](https://github.com/vercel/satori) which can take JSX and render it to an SVG without needing to spin up a browser. We then follow that up with the a library called [Sharp](https://github.com/lovell/sharp) to convert the SVG to an image format that can be used as an Open Graph image.
57+
58+
Let's take a look at some code for generating a very simple Open Graph image.
59+
60+
```jsx
61+
const svg = await satori(
62+
<div
63+
style={{
64+
background: "linear-gradient(orange, blue)",
65+
width: "100%",
66+
height: "100%",
67+
}}
68+
/>,
69+
{ width: 1200, height: 630 },
70+
)
71+
const webp = await sharp(Buffer.from(svg)).webp({ quality: 90 }).toBuffer()
72+
```
73+
74+
<div style="max-width: 500px; margin-inline: auto;">
75+
![Basic Open Graph
76+
Image](/articleAssets/2025-09/dynamic-og-images/og-image-basic.webp)
77+
</div>
78+
79+
In the above code there are a few sections we need to understand. The first section is the call to `satori`. This function takes two arguments. The first argument is the JSX that we want to render. The second argument is an object that contains options for Satori. You should always set the width and height to `1200` and `630` respectively since that is the recommended size for Open Graph images. We will look at other options later when we deal with fonts.
80+
81+
The second piece of code is the call to `sharp`. This function takes the SVG that was generated by Satori and converts it to a WebP image buffer. You can also convert it to other formats like PNG or JPEG if you prefer, but WebP is a great choice because it has good quality while keeping file sizes small. The `quality` option can be set from `1` to `100` with higher numbers resulting in better quality but larger file sizes.
82+
83+
<Tangent>
84+
Just because Satori uses JSX does not mean you need to be using React or
85+
another framework that uses JSX. You could instead write this same code using
86+
an object that is equivalent to the JSX output. Below is the same code as
87+
above but written using an object. If you want a more complicated example of
88+
using objects instead of JSX you can view [the exact code I use for generating
89+
Open Graph
90+
images](https://github.com/WebDevSimplified/Web-Dev-Simplified-Official-Blog/blob/master/src/utils/generateOpenGraphImage.js)
91+
for this blog.
92+
</Tangent>
93+
94+
```js
95+
const svg = await satori(
96+
{
97+
type: "div",
98+
props: {
99+
style: {
100+
background: "linear-gradient(orange, blue)",
101+
width: "100%",
102+
height: "100%",
103+
},
104+
},
105+
},
106+
{ width: 1200, height: 630 },
107+
)
108+
109+
const webp = await sharp(Buffer.from(svg)).webp({ quality: 90 }).toBuffer()
110+
```
111+
112+
### Adding Text
113+
114+
Most likely you want to add some text to your Open Graph images, which is a bit more complex since we now need to load a font file as well.
115+
116+
```jsx {1,18-25}
117+
const font = await fs.readFile("path/to/font.ttf")
118+
119+
const svg = await satori(
120+
<div
121+
style={{
122+
background: "linear-gradient(orange, blue)",
123+
width: "100%",
124+
height: "100%",
125+
fontFamily: "FontName",
126+
fontSize: "6rem",
127+
}}
128+
>
129+
Hello World
130+
</div>,
131+
{
132+
width: 1200,
133+
height: 630,
134+
fonts: [
135+
{
136+
name: "FontName",
137+
data: font,
138+
weight: 400,
139+
style: "normal",
140+
},
141+
],
142+
},
143+
)
144+
145+
const webp = await sharp(Buffer.from(svg)).webp({ quality: 90 }).toBuffer()
146+
```
147+
148+
<div style="max-width: 500px; margin-inline: auto;">
149+
![Open Graph Image Using
150+
Text](/articleAssets/2025-09/dynamic-og-images/og-image-text.webp)
151+
</div>
152+
153+
You can pass as many fonts as you want to the `fonts` option in Satori which is great for handling bold and non-bold text. Also, you cannot use variable fonts with Satori so make sure you have a separate font file for each weight you want to use.
154+
155+
### Adding Images
156+
157+
Most likely a plain background gradient isn't enough for your Open Graph images. So we will look at adding images next which need to be loaded and converted to base64 before being passed to Satori.
158+
159+
```jsx {2-5,23,32}
160+
const font = await fs.readFile("path/to/font.ttf")
161+
const logo = await fs.readFile("path/to/logo.webp", { encoding: "base64" })
162+
const background = await fs.readFile("path/to/background.svg", {
163+
encoding: "base64",
164+
})
165+
166+
const svg = await satori(
167+
<div
168+
style={{
169+
backgroundImage: "linear-gradient(orange, blue)",
170+
width: "100%",
171+
height: "100%",
172+
fontFamily: "FontName",
173+
fontSize: "6rem",
174+
display: "flex",
175+
flexDirection: "column",
176+
alignItems: "center",
177+
position: "relative",
178+
}}
179+
>
180+
<div
181+
style={{
182+
backgroundImage: `url("data:image/svg+xml;base64,${background}")`,
183+
width: "100%",
184+
height: "100%",
185+
position: "absolute",
186+
inset: 0,
187+
opacity: 0.4,
188+
}}
189+
/>
190+
<img
191+
src={`data:image/webp;base64,${image}`}
192+
style={{ width: "200px", height: "200px" }}
193+
/>
194+
<span>Hello World</span>
195+
</div>,
196+
{
197+
width: 1200,
198+
height: 630,
199+
fonts: [
200+
{
201+
name: "FontName",
202+
data: font,
203+
weight: 400,
204+
style: "normal",
205+
},
206+
],
207+
},
208+
)
209+
210+
const webp = await sharp(Buffer.from(svg)).webp({ quality: 90 }).toBuffer()
211+
```
212+
213+
<div style="max-width: 500px; margin-inline: auto;">
214+
![Open Graph Image Using Other
215+
Images](/articleAssets/2025-09/dynamic-og-images/og-image-images.webp)
216+
</div>
217+
218+
When you read your images with `fs` you can use the `encoding: "base64"` option to get the image as a base64 string which can then be used in an `img` tag or as a CSS background image. Just make sure you include the correct MIME type in the URL like `data:image/webp;base64,...` or `data:image/svg+xml;base64,...`.
219+
220+
## How To Use Open Graph Images
221+
222+
Now that you can create these images we need to talk about how to render them on your site. If you are using any form of JavaScript based static site generator you can just create a route that generates the image for each page and in that route just return a new response with the correct mime types. For example, if you are using Astro you could use a `og.webp.js` file with `staticPaths` to generate these files dynamically.
223+
224+
```js {4}
225+
const svg = await satori(/*...*/)
226+
const webp = await sharp(Buffer.from(svg)).webp({ quality: 90 }).toBuffer()
227+
228+
return new Response(webp, { headers: { "Content-Type": "image/webp" } })
229+
```
230+
231+
### Non-JS Sites
232+
233+
If your site doesn't use JavaScript you can instead just run this code as a JavaScript file from the command line or as part of your build process to generate all of the Open Graph images and save them to disk. You can use `fs.writeFile` to write the image buffer to a file like so:
234+
235+
```js
236+
import fs from "fs/promises"
237+
238+
const svg = await satori(/*...*/)
239+
const webp = await sharp(Buffer.from(svg)).webp({ quality: 90 }).toBuffer()
240+
241+
await fs.writeFile("path/to/output.webp", webp)
242+
```
243+
244+
## How To Add Open Graph Images to Your Site
245+
246+
The last thing we need to talk about is adding these images to your site. Luckily, this is quite easy. All you need to do is add a few meta tags to the `<head>` of your HTML document.
247+
248+
```html
249+
<meta
250+
property="og:title"
251+
content="How to Create Dynamic Open Graph Images Automatically for Your Site"
252+
/>
253+
<meta
254+
property="og:description"
255+
content="Learn how to automatically generate dynamic Open Graph images for your website using JavaScript."
256+
/>
257+
<meta
258+
property="og:image"
259+
content="https://blog.webdevsimplified.com/2025-09/dynamic-og-images/og-image.webp"
260+
/>
261+
<meta
262+
property="og:image:secure_url"
263+
content="https://blog.webdevsimplified.com/2025-09/dynamic-og-images/og-image.webp"
264+
/>
265+
<meta property="og:image:width" content="1200" />
266+
<meta property="og:image:height" content="630" />
267+
268+
<!-- X/Twitter Specific -->
269+
<meta name="twitter:card" content="summary_large_image" />
270+
<meta
271+
name="twitter:title"
272+
content="How to Create Dynamic Open Graph Images Automatically for Your Site"
273+
/>
274+
<meta
275+
name="twitter:description"
276+
content="Learn how to automatically generate dynamic Open Graph images for your website using JavaScript."
277+
/>
278+
<meta
279+
name="twitter:image"
280+
content="https://blog.webdevsimplified.com/2025-09/dynamic-og-images/og-image.webp"
281+
/>
282+
```
283+
284+
The most important tags are `og:image` and `twitter:image` since those are what actually specify the image to use. The `og:title`, `og:description`, `twitter:title`, and `twitter:description` tags are also important since they specify the title and description to use when your link is shared. The other tags are optional but can help improve how your link looks when shared.
285+
286+
## Conclusion
287+
288+
Creating dynamic Open Graph images is a great way to improve the appearance of your links when shared on social media and other platforms. By using `satori` and `sharp` you can easily generate these images automatically as part of your build process or on demand. This approach is fast, flexible, and works with any site regardless of the technology stack you are using.
289+
290+
If you want to see a full example of how I generate Open Graph images for this blog you can check out [the code on GitHub](https://github.com/WebDevSimplified/Web-Dev-Simplified-Official-Blog/blob/master/src/utils/generateOpenGraphImage.js).

0 commit comments

Comments
 (0)