In this guide, I'll demonstrate how to automate the addition of generated open-graph images to your Astro websites, automating yet another tedious task.
Open-graph Images
Here's a quick reminder about open-graph images. These are the images displayed when you share your link. They're easy to add—just insert the following meta
tag into your HTML's head
. However, while simple to add, they can be time-consuming to create and manage.
<meta property="og:image" content="path-to-my-image.png" />
Motivation
I recently shared my website template, Launch, which I use for all my websites including JXD, DocLabs, and Alphabee. It automates many of the tedious, yet crucial tasks related to managing a website, allowing me to spend more time developing the products. Automating the creation of images across all websites significantly saves me time.
Guide
Install the @vercel/og
package. This library is designed to convert React code into PNG images. It is built on Satori, a library that converts HTML and CSS into SVGs.
npm install @vercel/og
Create a new static resource endpoint for your images. In Launch, this is located at src/pages/blog/og/[...slug].png.ts
. However, you can create it elsewhere, provided that it:
- Resides in the
pages
directory - Includes the
...slug
path parameter - Has the
.png.ts
extension.
Add the following content:
export const prerender = true;
import type { APIRoute } from "astro";
import { ImageResponse } from "@vercel/og";
import { createElement as el } from "react";
import { readFileSync } from "fs";
// Load your posts however you like
import { fetchPosts } from "@/lib/blog";
type Props = {
title: string;
};
export const GET: APIRoute<Props> = ({ props }) => {
const interBold = readFileSync("./src/fonts/Inter-Bold.ttf");
return new ImageResponse(
el(
"div",
{
tw: "w-full h-full flex justify-center items-center bg-slate-50 text-5xl uppercase text-slate-800 font-bold",
style: {
fontFamily: "'Inter'",
},
},
props.title
),
{
width: 1200,
height: 630,
fonts: [
{
name: "Inter",
data: interBold,
style: "normal",
weight: 700,
},
],
}
);
};
export async function getStaticPaths() {
const posts = await fetchPosts();
return posts.map((post) => ({
params: { slug: post.slug },
props: { title: post.data.title },
}));
}
Let's take a deeper look at what is happening here.
First, we use export const prerender = true
, which instructs Astro to build these images at build time. This is possible because the images don't change, and we deploy a new version each time a new post is published. However, if your content comes from a different source like a CMS, consider using an edge function and caching the results.
We're loading a font for use in our images that we previously downloaded and stored in our source code.
An ImageResponse
is returned with our image content. This may appear unusual if you're accustomed to working with JSX. However, since Astro doesn't support .jsx
or .tsx
endpoints, we're using raw React with the createElement
function. In our div
, we use the tw
attribute to specify which Tailwind classes to apply. While I'm a huge fan of Tailwind, you could opt for inline styles if that's your preference.
Finally, we're defining a getStaticPaths
function to load our blog posts and pass the slug
and title
values.
We have now generated open graph images for each blog post. An example of this can be seen on Launch by visiting https://launch.jxd.dev/blog/og/hello-world.png.
Don't forget to add the meta
tag to your blog post content so your images are included.
<meta property="og:image" content={`/blog/og/${post.slug}.png`} />
Conclusion
That concludes the process of adding generated open-graph images to an Astro website. If you're planning to launch a new website from scratch, consider checking out Launch. It offers more than just open-graph images. It includes a functional blog, RSS feeds, a sitemap, robots.txt, analytics, and more.
Until next time ✌️