Dynamic OG Images in AstroJS with vercel/og

Last updated on
3 min read
Open Graph image generation concept showing a social media card with dynamic content

When building my portfolio’s blog, I wanted each post to have an attractive, dynamically generated social media preview image. In this post, I’ll explain how I implemented dynamic Open Graph (OG) images using Vercel’s OG Image Generation library with AstroJS.

Why Dynamic OG Images?

Social media preview images are crucial for engagement. When someone shares your blog post on Twitter, LinkedIn, or other platforms, an attractive preview image can significantly increase click-through rates. But creating these images manually for each post is time-consuming and maintaining consistency is challenging.

The Solution: Vercel’s OG Image Generation

Vercel provides a powerful package called @vercel/og that lets you generate images programmatically. Here’s why it’s great:

Implementation Overview

The implementation involves three main parts:

  1. Setting up the API endpoint
  2. Creating the image template
  3. Integrating with blog posts

Setting Up the API Endpoint

First, we need to create an API endpoint in Astro that will generate our images:

import { ImageResponse } from "@vercel/og";
import type { APIRoute } from "astro";

export const prerender = false;

export const GET: APIRoute = async ({ params }) => {
    try {
        // Load custom fonts
        const [playfairFont, robotoFont] = await Promise.all([
            fs.promises.readFile("./assets/PlayfairDisplay-Regular.woff"),
            fs.promises.readFile("./assets/Roboto-Regular.woff"),
        ]);

        // Create the image response
        const response = new ImageResponse(
            // Image JSX element
            {
                /* Image template */
            },
            {
                width: 1200,
                height: 630,
                fonts: [
                    {
                        name: "Playfair Display",
                        data: playfairFont,
                        style: "normal",
                    },
                    {
                        name: "Roboto",
                        data: robotoFont,
                        style: "normal",
                    },
                ],
            },
        );

        return response;
    } catch (error: any) {
        return new Response(`Failed to generate image: ${error.message}`, {
            status: 500,
        });
    }
};

Creating Beautiful Gradients

One of the key visual elements of my OG images is the gradient background. Here’s how I implemented it:

// Gradient background elements
{
    type: "div",
    props: {
        style: {
            position: "absolute",
            width: "250px",
            height: "500px",
            borderRadius: "999px",
            opacity: 0.4,
            filter: "blur(100px)",
            right: 0,
            top: -50,
            backgroundColor: "#C33764",
        },
    },
},
{
    type: "div",
    props: {
        style: {
            position: "absolute",
            width: "500px",
            height: "350px",
            borderRadius: "999px",
            opacity: 0.5,
            filter: "blur(200px)",
            right: 0,
            bottom: 0,
            backgroundColor: "#1D2671",
        },
    },
},

Performance Considerations

When implementing dynamic OG images, consider these performance tips:

  1. Caching: Add appropriate cache headers to prevent unnecessary regeneration
  2. Font Optimization: Subset fonts to include only necessary characters
  3. Error Handling: Implement proper fallbacks if generation fails

Common Challenges and Solutions

During implementation, I encountered several challenges:

1. Font Loading Issues

Initially, fonts weren’t loading consistently. The solution was to include fonts directly in the project and load them synchronously:

const fontData = await fs.promises.readFile(
    path.join(process.cwd(), "assets", "font.woff"),
);

2. Image Optimization

To ensure fast loading of profile images in the OG template:

const optimizedImage = await sharp(imageBuffer)
    .resize(90, 90, {
        fit: "cover",
        position: "center",
    })
    .grayscale()
    .toBuffer();

Next Steps

You can extend this system further by:

Conclusion

Dynamic OG image generation might seem like a small detail, but it significantly improves the sharing experience of your blog posts. With Vercel’s OG Image Generation, implementing this feature becomes straightforward while still offering plenty of customization options.

In the next post, we’ll look at implementing the interactive world map feature with dynamic tooltips!

Go back to the previous page