When sharing web content on social media, attractive preview images can significantly increase click-through rates. This article explains how to generate dynamic OG images in your HonoX project using Vercel OG.
What are OG Images?
OG (Open Graph) images are the preview images displayed when web pages are shared on social media platforms. They play a crucial role in capturing attention and improving engagement.
Traditionally, OG images were provided as static image files. However, with Vercel OG, you can now generate images dynamically based on your content.
Introduction to Vercel OG
Vercel OG is a library that uses JSX to generate dynamic OG images. It allows you to design images using HTML and CSS, and generate them dynamically based on content.
Key features:
- Declarative API using JSX
- CSS styling support
- Fonts, images, and emoji support
- Fast image generation at the edge
Installing the Required Package
First, install the necessary package:
npm install @vercel/og
Basic Implementation in HonoX
To implement Vercel OG in HonoX, you need to create a dedicated route. Here's a basic implementation example:
// routes/og.tsx
import { createRoute } from "honox/factory";
import { ImageResponse } from "@vercel/og";
export default createRoute((c) => {
// Generate OG image
return new ImageResponse(
(
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#f6f6f6",
padding: "40px",
}}
>
<h1
style={{
fontSize: 64,
fontWeight: 800,
color: "#1a202c",
}}
>
My Website
</h1>
<p
style={{
fontSize: 36,
color: "#4a5568",
}}
>
A blog about web development and technology
</p>
</div>
),
{
width: 1200,
height: 630,
}
);
});
Adding Dynamic Content
To create more practical OG images, you can use URL parameters to add dynamic content:
// routes/blogs/[slug]/og.tsx
import { createRoute } from "honox/factory";
import { ImageResponse } from "@vercel/og";
export default createRoute((c) => {
const slug = c.req.param("slug");
// Fetch blog post data here
// For example: const post = getBlogPost(slug);
// Placeholder blog title and description
const title = `Article about ${slug}`;
const description = "This is a description of the blog post.";
// Generate OG image
return new ImageResponse(
(
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#1a202c",
color: "white",
padding: "40px",
}}
>
<h1
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
marginBottom: 24,
}}
>
{title}
</h1>
<p
style={{
fontSize: 36,
color: "#a0aec0",
}}
>
{description}
</p>
</div>
),
{
width: 1200,
height: 630,
}
);
});
Embedding Images
To embed images like logos, use Base64 encoding:
import fs from "node:fs";
import path from "node:path";
// Load and encode the image
const iconPath = path.resolve("./public/icon.png");
const iconData = fs.readFileSync(iconPath);
const iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`;
// Use in JSX
<img src={iconBase64} width={140} height={140} alt="Logo" />;
Improving Design
Here's an example of a more refined OG image design:
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#0f172a",
background: "linear-gradient(145deg, #0f172a 0%, #1e293b 100%)",
padding: "0",
position: "relative",
overflow: "hidden",
fontFamily: "Inter, system-ui, sans-serif",
}}
>
{/* Decorative elements */}
<div
style={{
position: "absolute",
top: "-5%",
right: "-5%",
width: "30%",
height: "30%",
borderRadius: "24px",
background: "linear-gradient(225deg, rgba(56, 189, 248, 0.1) 0%, rgba(56, 189, 248, 0) 60%)",
transform: "rotate(15deg)",
display: "flex",
}}
/>
{/* Content container */}
<div
style={{
display: "flex",
width: "92%",
height: "92%",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "space-between",
borderRadius: "24px",
border: "1px solid rgba(255, 255, 255, 0.1)",
backgroundColor: "rgba(30, 41, 59, 0.7)",
backdropFilter: "blur(10px)",
padding: "48px",
position: "relative",
overflow: "hidden",
}}
>
{/* Title */}
<h1
style={{
display: "flex",
fontSize: "64px",
fontWeight: 800,
color: "#f8fafc",
marginBottom: "16px",
width: "100%",
lineHeight: 1.2,
letterSpacing: "-0.02em",
}}
>
Awesome Blog Title
</h1>
{/* Description */}
<p
style={{
display: "flex",
fontSize: "28px",
fontWeight: 400,
color: "#94a3b8",
marginBottom: "24px",
width: "90%",
lineHeight: 1.5,
}}
>
This article explains an interesting topic.
</p>
</div>
</div>
Important Implementation Notes
1. Explicit display: "flex"
Specification
In Vercel OG, div
elements with multiple child elements must explicitly specify display: "flex"
or display: "none"
:
// Good example
<div style={{ display: "flex" }}>
<child1 />
<child2 />
</div>
// Bad example - will throw an error
<div>
<child1 />
<child2 />
</div>
This is a constraint in how Vercel OG converts JSX to HTML. Make sure to specify this for all parent elements.
2. Using Custom Fonts
To use custom fonts, encode them as Base64:
// Load font
const fontData = fs.readFileSync("./assets/font.ttf");
const fontBase64 = fontData.toString("base64");
// Font settings
const options = {
width: 1200,
height: 630,
fonts: [
{
name: "MyFont",
data: fontBase64,
style: "normal",
},
],
};
// Generate OG image
return new ImageResponse(<div style={{ fontFamily: "MyFont" }}>Content</div>, options);
Integration with HonoX
By integrating with HonoX's routing system, you can provide various types of OG images:
- Default OG image for the entire site (
/og
) - Section-specific OG images (
/blogs/og
) - Individual OG images for each content piece (
/blogs/[slug]/og
)
By providing different designs and content for each route, you can enhance user experience.
Performance Considerations
Vercel OG provides fast image generation, but there are a few things to consider:
- Caching: It's recommended to cache OG images that don't change frequently on a CDN.
- Image Size Optimization: OG images typically use a size of 1200Γ630 pixels.
- Component Complexity: Very complex JSX components may affect generation time.
Best Practices for OG Image Optimization
Use .webp.tsx File Extension
It's essential to use the .webp.tsx
file extension for OG image routes:
// routes/[locale]/blogs/[slug]/og.webp.tsx
import { createRoute } from "honox/factory";
import { ImageResponse } from "@vercel/og";
export default createRoute((c) => {
// Generate OG image...
return new ImageResponse();
// JSX content
// ...
});
This specific file naming is not just a convention but a requirement for proper functionality. Using the .webp.tsx
extension ensures that:
- The files are correctly processed during the build process
- The images render properly in both development and production environments
- You maintain consistency between development and build outputs
Without this specific extension, the images may not display properly after building the project, creating inconsistencies between development and production environments.
Implement Post-Build WebP Conversion
While Vercel OG generates PNG images internally, you can optimize performance by automatically converting these to WebP format:
- Create a post-build script (like
convert-webp.js
in your scripts directory) - Configure it to find all PNG files generated for OG routes during build
- Convert these PNG files to WebP format for improved performance and smaller file sizes
// Example of a WebP conversion script
import sharp from "sharp";
import fs from "fs";
import path from "path";
// Find PNG files and convert them to WebP format
// This can reduce file sizes by 25-35% compared to PNG
This optimization technique ensures the best of both worlds: the stable generation capabilities of Vercel OG with PNG, plus the performance benefits of WebP delivery.
Handle Base64 Encoding Limitations
When working with embedded images (like logos or backgrounds) that need Base64 encoding, be aware that Vercel OG has limitations with WebP format. For maximum compatibility:
// Always use PNG for Base64 embedded images in OG components
const iconPath = path.resolve("./public/icon.png"); // Use PNG, not WebP
const iconData = fs.readFileSync(iconPath);
const iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`;
// Use in JSX
<img src={iconBase64} width={140} height={140} alt="Logo" />;
This approach ensures reliable image rendering in your OG components while still delivering the final output as optimized WebP images to end users through the conversion script.
Conclusion
By combining HonoX and Vercel OG, you can generate dynamic and attractive OG images. The main benefits are:
- Dynamically generate images based on content
- Flexible design using JSX and CSS
- Styling based on content
- Enhanced sharing experience on social media
- Optimized WebP format for improved performance
This makes sharing blog posts and web pages more attractive, leading to improved user engagement.