HonoXでVercel OGを使用した動的OG画像生成

HonoXプロジェクトでVercel OGを利用して動的なOG画像を生成する方法を解説します

投稿日: 2025年5月10日
更新日: 2025年5月11日
HonoXでVercel OGを使用した動的OG画像生成

ソーシャルメディアでWebサイトの記事がシェアされたとき、魅力的なプレビュー画像は格段にクリック率を高めることができます。この記事では、HonoXプロジェクトでVercel OGを使用して動的なOG画像を生成する方法を解説します。

OG画像とは

OG(Open Graph)画像は、ウェブページがソーシャルメディアでシェアされた際に表示される画像です。これは、ユーザーの注目を引き、クリック率を向上させる重要な要素となっています。

従来、OG画像は静的な画像ファイルとして提供されていましたが、Vercel OGを使用することで、動的にコンテンツを反映した画像を生成できるようになります。

Vercel OGの紹介

Vercel OGは、JSXを使って動的なOG画像を生成するためのライブラリです。HTMLとCSSを使って画像をデザインし、コンテンツに基づいて動的に生成することができます。

主な特徴:

  • JSXを使用した宣言的なAPI
  • CSSスタイルのサポート
  • フォント、画像、絵文字のサポート
  • エッジでの高速な画像生成

必要なパッケージのインストール

まず、必要なパッケージをインストールします:

HLJS BASH
npm install @vercel/og

HonoXプロジェクトでの基本的な実装

HonoXでVercel OGを実装するには、専用のルートを作成します。以下に基本的な実装例を示します:

HLJS TSX
// routes/og.tsx
import { createRoute } from "honox/factory";
import { ImageResponse } from "@vercel/og";

export default createRoute((c) => {
  // OG画像の生成
  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",
          }}
        >
          私のウェブサイト
        </h1>
        <p
          style={{
            fontSize: 36,
            color: "#4a5568",
          }}
        >
          ウェブ開発と技術についてのブログ
        </p>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  );
});

動的なコンテンツの追加

より実用的なOG画像を生成するために、URLパラメータなどを利用して動的な内容を追加できます:

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");

  // ここでブログ記事のデータを取得する処理
  // 例えば:const post = getBlogPost(slug);

  // 仮のブログタイトルとデスクリプション
  const title = `${slug}についての記事`;
  const description = "これはブログ記事の説明文です。";

  // OG画像を生成
  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,
    }
  );
});

画像の埋め込み

ロゴなどの画像を埋め込むには、Base64エンコーディングを使用します:

HLJS TSX
import fs from "node:fs";
import path from "node:path";

// 画像の読み込みと変換
const iconPath = path.resolve("./public/icon.png");
const iconData = fs.readFileSync(iconPath);
const iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`;

// JSXで使用
<img src={iconBase64} width={140} height={140} alt="ロゴ" />;

デザインの改善

より洗練されたOG画像を作成するためのデザイン例を見てみましょう:

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",
  }}
>
  {/* 装飾要素 */}
  <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",
    }}
  />

  {/* コンテンツコンテナ */}
  <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",
    }}
  >
    {/* タイトル */}
    <h1
      style={{
        display: "flex",
        fontSize: "64px",
        fontWeight: 800,
        color: "#f8fafc",
        marginBottom: "16px",
        width: "100%",
        lineHeight: 1.2,
        letterSpacing: "-0.02em",
      }}
    >
      素晴らしいブログタイトル
    </h1>

    {/* 説明文 */}
    <p
      style={{
        display: "flex",
        fontSize: "28px",
        fontWeight: 400,
        color: "#94a3b8",
        marginBottom: "24px",
        width: "90%",
        lineHeight: 1.5,
      }}
    >
      この記事では興味深いトピックについて解説します。
    </p>
  </div>
</div>

注意点と実装のポイント

1. display: "flex" の明示的な指定

Vercel OGでは、複数の子要素を持つdiv要素には明示的にdisplay: "flex"またはdisplay: "none"を指定する必要があります。

HLJS TSX
// 良い例
<div style={{ display: "flex" }}>
  <child1 />
  <child2 />
</div>

// 悪い例 - エラーが発生します
<div>
  <child1 />
  <child2 />
</div>

これは、Vercel OGがJSXをHTMLに変換する際の制約です。すべての親要素にこの指定を忘れないようにしましょう。

2. フォントの使用

カスタムフォントを使用するには、Base64でエンコードして読み込む必要があります:

HLJS TSX
// フォントの読み込み
const fontData = fs.readFileSync("./assets/font.ttf");
const fontBase64 = fontData.toString("base64");

// フォント設定
const options = {
  width: 1200,
  height: 630,
  fonts: [
    {
      name: "MyFont",
      data: fontBase64,
      style: "normal",
    },
  ],
};

// OG画像生成
return new ImageResponse(<div style={{ fontFamily: "MyFont" }}>コンテンツ</div>, options);

HonoXでの統合

HonoXのルーティングシステムと統合することで、様々なタイプのOG画像を提供できます:

  1. サイト全体のデフォルトOG画像 (/og)
  2. セクションごとのOG画像 (/blogs/og)
  3. 各コンテンツごとの個別OG画像 (/blogs/[slug]/og)

それぞれのルートで異なるデザインやコンテンツを提供することで、ユーザーエクスペリエンスを向上させることができます。

パフォーマンスの考慮事項

Vercel OGは高速な画像生成を提供しますが、いくつかの点を考慮する必要があります:

  1. キャッシュ: 頻繁に変更されないOG画像はCDNでキャッシュすることをお勧めします。
  2. 画像サイズの最適化: OG画像は通常1200×630ピクセルのサイズを使用します。
  3. コンポーネントの複雑さ: 非常に複雑なJSXコンポーネントは生成時間に影響する可能性があります。

OG画像最適化のベストプラクティス

.webp.tsx ファイル拡張子を使用する

OG画像ルートには.webp.tsxファイル拡張子を使用することが必要不可欠です:

HLJS TS
// routes/[locale]/blogs/[slug]/og.webp.tsx
import { createRoute } from "honox/factory";
import { ImageResponse } from "@vercel/og";

export default createRoute((c) => {
  // OG画像を生成...
  return new ImageResponse();
  // JSXコンテンツ
  // ...
});

この特定のファイル命名は単なる規約ではなく、正しい機能を確保するための要件です。.webp.tsx拡張子を使用することで:

  1. ビルドプロセス中にファイルが正しく処理される
  2. 開発環境と本番環境の両方で画像が適切にレンダリングされる
  3. 開発環境とビルド出力の間の一貫性が維持される

この特定の拡張子を使用しない場合、プロジェクトのビルド後に画像が正しく表示されない可能性があり、開発環境と本番環境の間で不整合が生じます。

ビルド後のWebP変換を実装する

Vercel OGは内部的にPNG画像を生成しますが、これらをWebP形式に自動変換することでパフォーマンスを最適化できます:

  1. ビルド後に実行されるスクリプト(例:scriptsディレクトリにconvert-webp.js)を作成します
  2. ビルド中にOGルート用に生成されたすべてのPNGファイルを検索するよう設定します
  3. これらのPNGファイルをパフォーマンス向上とファイルサイズ削減のためにWebP形式に変換します
HLJS JS
// WebP変換スクリプトの例
import sharp from "sharp";
import fs from "fs";
import path from "path";

// PNGファイルを見つけてWebPに変換
// これによりPNGと比較してファイルサイズが25-35%削減できます

この最適化手法により、PNGを使用したVercel OGの安定した画像生成能力と、WebP配信によるパフォーマンス上の利点の両方を実現できます。

Base64エンコーディングの制限に対応する

Base64エンコーディングが必要な埋め込み画像(ロゴや背景など)を使用する場合、Vercel OGはWebP形式に制限があることに注意してください。最大限の互換性を確保するには:

HLJS TSX
// OGコンポーネント内のBase64埋め込み画像には常にPNGを使用する
const iconPath = path.resolve("./public/icon.png"); // WebPではなくPNGを使用
const iconData = fs.readFileSync(iconPath);
const iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`;

// JSXで使用
<img src={iconBase64} width={140} height={140} alt="ロゴ" />;

このアプローチにより、変換スクリプトを通じて最終的に最適化されたWebP画像をユーザーに配信しながら、OGコンポーネント内での画像レンダリングの信頼性を確保できます。

まとめ

HonoXとVercel OGを組み合わせることで、動的で魅力的なOG画像を生成できるようになりました。主な利点は:

  • コンテンツに基づいて動的に画像を生成
  • JSXとCSSを使った柔軟なデザイン
  • コンテンツ内容に応じたスタイリング
  • ソーシャルメディアでの共有体験を向上
  • 最適化されたWebP形式による性能向上

これにより、ブログ記事やウェブページの共有がより魅力的になり、ユーザーエンゲージメントの向上が期待できます。