Dynamic OG Images in AstroJS with vercel/og
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:
- JSX-like Syntax: Write your image layout using familiar React-like components
- Custom Fonts: Support for custom fonts to match your brand
- Optimized Performance: Images are generated on-demand and cached
- Gradient Support: Create beautiful gradients for visual appeal
Implementation Overview
The implementation involves three main parts:
- Setting up the API endpoint
- Creating the image template
- 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:
- Caching: Add appropriate cache headers to prevent unnecessary regeneration
- Font Optimization: Subset fonts to include only necessary characters
- 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:
- Adding more dynamic elements based on post content
- Implementing different templates for different types of content
- Adding animated elements for platforms that support them
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