Dynamic OG Image Generation with Vercel OG in HonoX

Learn how to generate dynamic Open Graph images using Vercel OG in your HonoX project

Posted on: May 10, 2025
Updated on: May 11, 2025
Dynamic OG Image Generation with Vercel OG in HonoX
This content has been translated by AI from the original Japanese version.

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:

HLJS BASH
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:

HLJS TSX
// 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:

HLJS TSX
// 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:

HLJS TSX
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:

HLJS TSX
<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":

HLJS TSX
// 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:

HLJS TSX
// 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:

  1. Default OG image for the entire site (/og)
  2. Section-specific OG images (/blogs/og)
  3. 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:

  1. Caching: It's recommended to cache OG images that don't change frequently on a CDN.
  2. Image Size Optimization: OG images typically use a size of 1200Γ—630 pixels.
  3. 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:

HLJS TS
// 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:

  1. The files are correctly processed during the build process
  2. The images render properly in both development and production environments
  3. 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:

  1. Create a post-build script (like convert-webp.js in your scripts directory)
  2. Configure it to find all PNG files generated for OG routes during build
  3. Convert these PNG files to WebP format for improved performance and smaller file sizes
HLJS JS
// 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:

HLJS TSX
// 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.