0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.jsで動的にOG画像を生成した話

Posted at

初めに

業務でSNSへのシェア機能を実装しました。
要件でユーザごとに投稿するときのOGイメージは一意であることとあったので動的にOG画像を生成する必要がありました。
結構苦戦したので備忘録として実際に Next.js(App Router)で動的に OG 画像を生成した実装例と、そのポイントをまとめます。

OGって何?

OG画像(Open Graph Image) とは、
SNS で URL を貼ったときに表示される “タイトル・説明・画像” を指定する仕組み です。
Web ページの タグで以下のように指定します。

<meta property="og:title" content="記事タイトル">
<meta property="og:description" content="説明文">
<meta property="og:image" content="https://example.com/og.png">

Twitterとかでよく出てくるこんなやつ
metaタグのog:imageの画像がSNS上に表示される
今回はこの画像を動的に生成する
image.png

どうやって実現したか

Next.jsに機能として提供されていたのでそちらを使いました

Next.js の動的 OG は opengraph-image.tsx をルート直下に置くと、そのルートの OG が生成されます。
今回はユーザごとに異なる画像を出したかったので、

app/
  design/
    [shareId]/
      page.tsx
      opengraph-image.tsx

のような構成にしました

具体的な実装

公式に書いてあるとおり以下のような形で実現できます

import { ImageResponse } from "next/og";

export const size = {
  width: 1200,
  height: 630,
};
export const contentType = "image/png";

export default async function Image({ params }: { params: { slug: string } }) {
  const title = decodeURIComponent(params.slug);

  return new ImageResponse(
    (
      <div
        style={{
          display: "flex",
          width: "100%",
          height: "100%",
          background: "#111",
          color: "white",
          alignItems: "center",
          justifyContent: "center",
          fontSize: 48,
          fontWeight: 700,
        }}
      >
        {title}
      </div>
    ),
    size
  );
}

上記のファイル中でAPIへのリクエストも可能です。
私はAPIから必要な情報を取得して、表示情報を組み立てるような実装をしました。

デバッグする時は以下のOG確認サイトを使って表示を見ました

ローカル環境で確認する手順はこちらの記事を参考にしました

ハマったポイント

レイアウト作成

表示をすぐに確認できない+class名を確認できない+使えるCSSに制限がある
という状態で実装に苦労しました。

サポートされているCSSはこちらに記載があります。

Vercelがplayground環境を用意してくれているので最初はそちらで触ってみるのがいいかもしれません

styleの可読性

classが使えないので要素に直接スタイルを書くことになります。
構造がわかりづらかったので対策としてconstで各要素のstyleを管理しました。

// app/share/[userId]/opengraph-image.tsx
import { ImageResponse } from "next/og";

export const size = {
  width: 1200,
  height: 630,
};
export const contentType = "image/png";

// ⭐ ここ:スタイルを const で分割して可読性UP
const containerStyle = {
  display: "flex",
  width: "100%",
  height: "100%",
  background: "#111",
  color: "white",
  padding: "60px",
  flexDirection: "column",
  justifyContent: "space-between",
};

const titleStyle = {
  fontSize: 56,
  fontWeight: 700,
};

const subTextStyle = {
  fontSize: 32,
  opacity: 0.8,
};

export default async function Image({ params }: { params: { userId: string } }) {
  const { userId } = params;

  // ダミーで一意の情報を生成
  const user = {
    name: `User ${userId}`,
    followers: Math.floor(Math.random() * 1000),
  };

  return new ImageResponse(
    (
      <div style={containerStyle}>
        <div style={titleStyle}>{user.name} の投稿</div>
        <div style={subTextStyle}>フォロワー:{user.followers}</div>
      </div>
    ),
    size
  );
}

page側でのmetaタグの指定

page側のopenGraphイメージに自分でファイル指定を入れるとうまく動きませんでした。
自分で指定するとNext.js側がよしなにやってくれる設定を上書きしてしまうみたいで、画像が表示されない!!!と困ってました

page.tsx
export async function generateMetadata({ params }) {
  const title = decodeURIComponent(params.slug);
  return {
    title,
    description,
    ///何も指定しなくてもNext.js側が設定してくれるのでOK
    //openGraph: {
    //},
  };
}

日本語フォントが読めない!!!!

デフォルトだと日本語フォントが完全にサポートされていないらしく自分で読み込む必要があります
単純に日本語を出すと「□ □ □ 」と表示されたのでこの対応は必須かもしれません。

こちらの記事に対処法が載っていました

GoogleFontsからフォントをダウンロードしてフォントをサブセット化(必要な文字だけ抜き出す)して軽量化してプロジェクトフォルダに配置しました。

まとめ

クセがありますが簡単な内容ならすぐに動的OGが実現できると思います。
投稿してみたらある程度時間が経たないと画像が表示されない場合がある(Twitter側のクロールタイミングが安定しない?)など、Twitter側の仕様にも悩まされました。
今回はTwitterへの共有機能を作ったので時間がある時に違うSNSへシェアする時はどうすればいいのかも調べてみます

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?