ノベルティメディア

media

Next.jsと@vercel/ogで動的なOGP画像を生成する方法

Next.jsと@vercel/ogで動的なOGP画像を生成する方法

こんにちは。ノベルティの高宮です。

今回はNext.jsとvercel/ogを使って動的なOGP画像を生成する方法について解説します。

OGPは自社メディアの場合、各投稿やページに個別のOGPを設定することで、そのページの独自性を強調し、ユーザーの関心を引きつけることができます。

しかし、ユーザーがコンテンツを投稿するタイプのアプリケーションでは、すべての投稿に同じデフォルトのOGP画像が使用されることが一般的です。

その結果、個々の投稿が持つユニークな魅力をソーシャルメディア上で効果的に伝えることが難しくなります。

このコラムでは、Next.jsとvercel/ogを用いて、ユーザーの投稿に応じた動的OGPを生成する方法を紹介します。

この方法を使えば、ユーザーの投稿内容に基づいて独自のOGP画像を生成することができるようになります。また、応用することで動的なサムネイル画像も生成することができるので、参考にしてみてください。

使用したバージョン

  • Next.js:14.0.4(Pages Router)
  • @vercel/og:0.6.1

実装方法

ベースとなる画像を作成する

まず一番初めにベースとなる画像を作成します。この画像は、OGP画像の土台となり、この画像の上にタイトルが追加されます。

今回は以下のような画像を作成しました。

OGPのベース画像。

ベースの画像を作成する上で何点か気をつけるべきことがあります。

まず、サイズについてですがFacebookやXなど、主要なソーシャルメディアプラットフォームで推奨されている1200×630ピクセルの大きさで作成しましょう。

画像の上に載る文字の量や種類は投稿によって異なるため、ベース画像のデザインはどのような文字が入っても破綻しないよう、大きめの余白を確保しシンプルなデザインにするといいでしょう。

APIを実装する

ここからは実際にコードを書いていきます。

まずは@vercel/ogをインストール

$ npm install @vercel/og

次に/pages/api/og.tsxを作成して処理を書いていきます。

ここではリクエストのクエリパラメーターからタイトルやユーザー名などのOGPに出力する情報を取得し、HTMLとCSS(JSX)でマークアップすることでOGPを作成します。

ImageResponseにマークアップしたJSXを渡してreturnすれば画像に変換してくれます。便利。

コードの全体像

import { ImageResponse } from "@vercel/og";
import { NextRequest } from "next/server";

export const config = {
    runtime: "edge",
};

export default async function handler(req: NextRequest) {
    const { searchParams } = new URL(req.url);
    const title =
        searchParams.get("title")?.slice(0, 100) || "デフォルトのタイトル";
    const userName = searchParams.get("userName") || "デフォルトのユーザー名";
    const userImageUrl =
        searchParams.get("userImageUrl") ||
        `${process.env.NEXT_PUBLIC_BASE_URL}/default_user.jpg`;

    return new ImageResponse(
        (
            <div
                style={{
                    backgroundImage: `url(${process.env.NEXT_PUBLIC_BASE_URL}/ogp_base.jpg)`,
                    backgroundSize: "100% 100%",
                    height: "100%",
                    width: "100%",
                    display: "flex",
                    padding: "0 120px",
                    flexDirection: "column",
                    justifyContent: "center",
                }}
            >
                <p
                    style={{
                        fontSize: 50,
                        color: "1E1E1E",
                        textAlign: "left",
                        lineHeight: 1.5,
                        fontWeight: "bold",
                    }}
                >
                    {title}
                </p>
                <div
                    style={{
                        display: "flex",
                        alignItems: "center",
                        gap: 20,
                    }}
                >
                    <div
                        style={{
                            backgroundImage: `url(${userImageUrl})`,
                            backgroundSize: "100% 100%",
                            height: 60,
                            width: 60,
                            borderRadius: "50%",
                            border: "2px solid #1E1E1E",
                        }}
                    ></div>
                    <div
                        style={{
                            fontSize: 18,
                            color: "1E1E1E",
                            fontWeight: "bold",
                        }}
                    >
                        {userName}
                    </div>
                </div>
            </div>
        ),
        {
            width: 1200,
            height: 630,
        }
    );
}

注意点

複数の子要素を含む要素をマークアップしたら、以下のエラーが発生しました。

[cause]: Error: Expected <div> to have explicit "display: flex" or "display: none" if it has more than one child node.

複数の子要素を持つ <div> 要素に対して、明示的に display: flex または display: none を指定する必要があったみたいです。

この他にもマークアップの制限があるみたいなので以下のリポジトリを参考にしてみてください。

HTML

CSS

OGPを出力したいページでAPIを呼び出す

次にOGPを出力するページで実装したAPIを呼び出します。

今回の例ではgetStaticPropsで取得した投稿データをプロップスで渡し、そのデータからタイトルとユーザーネーム、ユーザー画像のURLを渡しています。

※今回はクエリパラメーターで画像のURLを渡していますが、要件によってセキュリティ対策を!

const Media = ({ post }: { post: Post }) => {
    return (
        <>
            <Head>
                <meta
                    property="og:image"
                    content={`${process.env.NEXT_PUBLIC_BASE_URL}/api/og?title=${post.title}&userName=${post.user.name}&uesrImageUrl=${post.user.imageUrl}`}
                />
                {/* 省略 */}
            </Head>
            {/* 省略 */}
        </>
    );
};
export const getStaticProps: GetStaticProps = async (ctx) => {
    const id = ctx.params?.id ?? "";
    const post = await getPost(id)
  
    return {
        props: {
            post,
        },
    };
};
export default Media;

本番環境にデプロイして確認

ここまでマークアップしたら本番環境にデプロイを行います。

実際にXで確認してみるとちゃんとOGPが出力されています。

Xの共有画面。Next.jsでのフォーム実装とアクセシビリティ対応の考え方という内容の投稿をXで共有する画面

応用方法

今回は@vercel/ogを使って動的なOGP画像を出力しましたが、ただ出力するだけでなく、色々な使い方ができます。

例1:複数のデザインを出し分ける。

クエリパラメーターによって出力するデザインを変更することができるので、投稿者の職種や投稿のカテゴリーによってデザインを変えるなどの工夫ができます。

実装方法やアーキテクチャは異なりますが、同じようなことをクックパットさんの記事にまとまっていたので参考にしてみてください。

例2:投稿のサムネイルとして表示する

OGP同様に投稿のサムネイルにも動的な画像を表示することができます。処理はほぼ同じなので、APIの呼び出しを適当な位置で行うことで表示できます。

ただし、サイズや比率などをデザインに応じて最適化する必要があるので、注意してください。

記事はできているけど、サムネイルがまだできてない。仮画像を探すのが面倒。といった時は一旦こちらを出しておいて、サムネイルが入稿されたら切り替わるように処理を加えておくといいでしょう。

まとめ

Next.jsと@vercel/ogで動的OGPを生成する方法について説明しました。

ノベルティではアクセシビリティ、ユーザビリティに配慮した実装やJamstackでのサイト制作にも力をいれていますので、ぜひお任せください!

興味のある方はお問い合わせを!

この記事をシェアする

Webプロモーション・業務改善は
ノベルティひとつで完結

はじめての依頼にも
全力でサポートさせていただきます

メールでのお問い合わせ

おすすめ記事/ PICKUP

    記事カテゴリー/ CATEGORY

      Webプロモーションや業務改善・DX化

      企業の課題はノベルティひとつで完結

      ホームページ制作などのWeb制作をはじめ、
      システム開発やマーケティング支援などワンストップで対応
      まずはお気軽にお問い合わせください

      お問い合わせ

      お電話またはメールでお気軽にお問い合わせください。