【OGP】Next.js + microCMSを使って、リンクカードを作ってみた

目次
こんにちは、さかです。
この記事では、Next.jsとmicroCMSでOGP 情報を使い、リンクカードを作成する方法を解説します。
はじめに
SNSが盛んに利用されている近年では、OGPはとても重要です。
OGPを設定することで、TwitterやFacebookなどのSNSでリンクがシェアされた際に、タイトル、画像、説明文が意図した通りに表示され、ユーザーの目を引くことができます。
webページにおいても、OGP情報を使用して、リンクカードにすることで、リンク先の情報を一目で把握でき、ユーザーの視認性を高める効果があります。
まだ自分のサイトではOGP情報を使用したリンクカード生成をしていなかったため、今回新たにリンクカードに対応するように改修をしました。
OGPとは
OGP(Open Graph Protocol)とは、ウェブページの情報をSNSに適切に伝えるための仕組みです。ウェブサイトのURLをSNSに貼り付けると、そのページのタイトルや説明文、画像が自動的に表示されます。これがOGPの基本的な機能です。
以下は、OGP タグの例です。
<meta property="og:title" content="ページのタイトル">
<meta property="og:description" content="ページの説明文">
<meta property="og:image" content="画像のURL">
<meta property="og:url" content="ページのURL">
OGPが重要な理由
OGPが重要とされる主な理由は以下の通りです。
- ・クリック率の向上:魅力的な画像やタイトルが表示されることで、ユーザーがリンクをクリックする可能性が高まります。
- ・ブランディング:サイトのイメージに合った情報が表示されることで、ブランドイメージを統一できます。
- ・情報の正確性:意図しない情報が表示されることを防ぎ、正確な情報をユーザーに届けられます。
作成するリンクカードの全体像
今回はNext.jsで、以下のOGP情報を使用したリンクカードを実装していきます。
- ・外部サイトのOGP情報を取得する。
- ・microCMSのリッチエディタに設定した「link-card」カスタムクラスを使って、自動的にリンクカードを生成する。
- ・ドメイン判定による、表示方法の最適化。
- ・取得したOGP情報をカード形式で表示する。
技術スタック
今回の実装で使用する主な技術は以下の通りです。
- ・Next.js 15.0.3
- ・React
- ・typescript
- ・open-graph-scraper
- ・html-react-parser
- ・microCMS
- ・tailwindcss
microCMSリッチエディタの設定
microCMSのリッチエディタでは、コンテンツにカスタムクラス「link-card」を設定することで、特定のテキストをリンクカードとして表示させ、その部分がLinkCardコンポーネントに置き換わるようにします。
リッチエディタにカスタムclassを追加する
まず、画面右上にあるAPI設定をクリックし、APIスキーマ > リッチエディタに遷移します。
その後、リッチエディタが適用されている箇所の詳細設定を選択します。

その後、サイドバナーが出現し、カスタムclassの設定までスクロールし、リンクカードのカスタムクラスを追加します。

リッチエディタにカスタムクラスを付与する
保存したら、リッチエディタの編集画面に移動し、カスタムを選択すると、先ほど追加したリンクカードが追加されています。

リンクカードにしたいテキストURLをマーカーし、カスタムクラスの「link-card」を付与することができます。

このように設定することで、リンクを解析し、LinkCardコンポーネントに置き換わることができます。
OGP取得機能の実装とリンクカード表示
まず、外部のウェブサイトのOGP情報を取得する機能を実装します。
これには、open-graph-scraper
という外部サイトのOGP情報を簡単に取得できるライブラリが便利です。
Server Actionsを使用する
open-graph-scraper
はServer Actionsで実行します。
Server Actionsを使用することで、クライアントサイド(ブラウザ)から直接外部サイトにアクセスしようとした際に発生するCORS(Cross-Origin Resource Sharing) の問題を回避できます。
以下は、Server Actionsを使用して、OGP情報を取得する関数を実装します。
'use server';
import ogs from 'open-graph-scraper';
interface OgpResult {
title: string;
image: string;
domain: string;
url: string;
favicon: string;
}
export async function getOgp(url: string): Promise<OgpResult> {
const domain = new URL(url).hostname; // URLからドメイン名を取得
const defaultFavicon = new URL('/favicon.ico', url).toString(); // デフォルトのファビコンURL
try {
const { result } = await ogs({ url }); // open-graph-scraperでOGP情報を取得
// 取得した情報からタイトル、画像、ファビコンを抽出
// 型アサーションを使って型を明示的に指定
const title = (result.ogTitle as string) ?? '';
const image = (result.ogImage && result.ogImage[0]?.url as string) ?? ''; // 画像は配列で返ってくることがあるので注意
const favicon = (result.favicon as string) ?? defaultFavicon; // ファビコンがなければデフォルト画像を適用する
return { title, image, domain, url, favicon };
} catch (error) {
// エラーが発生した場合も、最低限の情報を返す
console.error(`OGP取得エラー for ${url}:`, error);
return { title: '', image: '', domain, url, favicon: defaultFavicon };
}
}
use server
では、このファイル内の関数がサーバーサイドでのみ実行されることを示し、ogs({ url })
で、open-graph-scraper
ライブラリを使用して、指定されたURLのOGP情報を取得します。
result.ogTitle
などは、取得したOGP情報からタイトル、画像、ファビコンのURLを取り出す処理です。?? ''
は、情報が取得できなかった場合に空文字列を返します。
エラーハンドリング は、OGP情報の取得に失敗した場合でも、エラーを捕捉して最低限の情報を返すようにしています。
LinkCardコンポーネントの実装とmicroCMS連携
OGP情報を表示するためのLinkCardコンポーネントと、microCMSのリッチエディタと連携する部分を実装します。
まずは、OGP情報を表示するLinkCardコンポーネントを実装します。
import Link from 'next/link';
import Image from 'next/image';
import { getOgp } from '@/app/_action/getOgp'; // サーバーアクションをインポート
interface LinkCardProps {
url: string;
}
const LinkCard = async ({ url }: LinkCardProps) => {
// サーバーアクションを呼び出してOGP情報を取得
const ogp = await getOgp(url);
const serverDomain = process.env.SERVER_DOMAIN; // 環境変数から自分のサイトのドメインを取得
let isDomainFlag = false; // 自分のサイト内のリンクかどうかを判定するフラグ
// --- 同ドメイン判定ロジック ---
if (serverDomain) {
// www. の有無を無視してドメインを比較するため、正規化します
const normalizedDomain = ogp.domain.replace(/^www\./, '');
const normalizedServerDomain = new URL(serverDomain).hostname.replace(/^www\./, '');
if (normalizedDomain === normalizedServerDomain) {
// 自分のサイト内のリンクの場合、パスだけにする
const parsed = new URL(ogp.url);
ogp.url = parsed.pathname + parsed.search + parsed.hash;
isDomainFlag = true;
}
}
return (
// Linkコンポーネントでリンク全体を囲む
<Link
href={ogp.url}
target={isDomainFlag ? '_self' : '_blank'} // 自分のサイトなら同じタブで、外部サイトなら新しいタブで開く
rel={isDomainFlag ? '' : 'noopener noreferrer'} // セキュリティ対策
className="block border border-gray-200 rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow duration-300 mb-4"
>
<div className="flex items-center p-4">
{ogp.image ? (
<div className="w-24 h-24 mr-4 flex-shrink-0">
<Image
src={ogp.image}
alt={ogp.title || 'OGP Image'}
width={96}
height={96}
className="object-cover rounded-md"
/>
</div>
) : (
<div className="w-24 h-24 mr-4 flex-shrink-0 bg-gray-100 flex items-center justify- center rounded-md text-gray-500">
{/* 画像がない場合のプレースホルダー */}
No Image
</div>
)}
<div className="flex-grow">
<h3 className="text-lg font-semibold text-gray-800 line-clamp-2">
{ogp.title || ogp.url}
</h3>
<p className="text-sm text-gray-600 line-clamp-1 mt-1">
{ogp.domain}
</p>
</div>
</div>
</Link>
);
};
export default LinkCard;
await getOgp(url)
で先ほど作成したサーバーアクションを呼び出して、非同期でOGP情報を取得します。
また、process.env.SERVER_DOMAIN
で環境変数から自分のサイトのドメインを取得します。
Next.jsでは、.env
ファイルに設定した環境変数をprocess.env.変数名で参照できます。ここに自分のサイトのドメインを設定しておきます。
同ドメイン判定ロジックについては、自分のサイト内のリンク(同ドメイン)であれば、ogp.url
を相対パス(/about
のような形式)に変換しています。これにより、Next.jsのLinkコンポーネントが内部リンクとして最適に扱えるようになります。
isDomainFlag
を使用して、自分のサイト内のリンクであればtarget="_self"
(同じタブで開く)、外部サイトへのリンクであればtarget="_blank"
(新しいタブで開く)を設定しています。
rel="noopener noreferrer"
は、target="_blank"
を使用する際のセキュリティ対策です。
Linkコンポーネントの中に、OGPで取得した画像、タイトル、ドメイン名を表示するUIをtailwindcssで作成しています。また、Imageコンポーネントを使用することで、画像の最適化が行われます。
ArticleRichEditorコンポーネントでparse処理する
html-react-parser
というライブラリで、カスタムクラスを認識するロジックを`ArticleRichEditor`コンポーネントに組み込みます。
// features/article/components/ArticleRichEditor/ArticleRichEditor.tsx
import parse, { DOMNode, Element } from 'html-react-parser';
import React from 'react';
import LinkCard from '../LinkCard/LinkCard'; // 作成したLinkCardコンポーネントをインポート
type ArticleRichEditorProps = {
richEditor: string; // microCMSから取得したHTML文字列
};
const ArticleRichEditor = async ({
richEditor,
}: ArticleRichEditorProps): Promise<React.ReactElement> => {
// microCMSのリッチエディタのカスタムクラス「link-card」に対応するための正規化処理
// span.link-card を含む様々なパターンを a.link-card に正規化します。
// これにより、後続のparse処理でaタグとして認識しやすくなります。
const normalizedHtml = richEditor
// <p><span class="link-card">URL</span></p> パターンを a.link-card に変換
.replace(
/<p[^>]*>\s*<span class="link-card">([^<]+)<\/span>\s*<\/p>/g,
'<a href="$1" class="link-card">$1</a>',
)
// <span class="link-card">URL</span> 単体パターンを a.link-card に変換
.replace(
/<span class="link-card">([^<]+)<\/span>/g,
'<a href="$1" class="link-card">$1</a>',
)
// 既存のaタグ内に span.link-card があるパターンも a.link-card に変換
.replace(
/<a([^>]*)href="([^"]+)"([^>]*)>\s*<span class="link-card">(.*?)<\/span>\s*<\/a>/g,
'<a href="$2" class="link-card">$4</a>',
);
const options = {
// HTMLの各ノードを解析する際のオプション
replace: (domNode: DOMNode) => {
// もし現在のノードがHTML要素(例: <a>タグ)で、
// かつ、そのタグ名が'a'であり、
// さらに、class属性に'link-card'が含まれている場合
if (
domNode instanceof Element &&
domNode.name === 'a' &&
domNode.attribs.class?.split(' ').includes('link-card')
) {
// そのaタグのhref属性からURLを取得
const href = domNode.attribs.href;
// 取得したURLを使ってLinkCardコンポーネントをレンダリング
return <LinkCard url={href} />;
}
// それ以外のノードはデフォルトの処理に任せる(そのままレンダリングする)
},
};
return (
<div className="aricleContents pb-14">{parse(normalizedHtml, options)}</div>
);
};
export default ArticleRichEditor;
このコンポーネントは、microCMSのリッチエディタで設定されたカスタムクラス「link-card」を解析し、リンクカードを表示する処理をしています。
normalizedHtml
による正規化処理によって、microCMSのリッチエディタの出力は、カスタムクラスの適用方法によって<span>
タグに包まれたり、<p>
タグに包まれたりする可能性があります。normalizedHtml
の処理は、これらの様々なパターンを統一的に<a href="URL" class="link-card">URL</a>
という形式に変換し、後続のhtml-react-parser
が扱いやすいようにします。
html-react-parser
でこのライブラリは、HTML文字列をReactコンポーネントのツリーに変換してくれます。
また、parse関数のoptions内でreplace
プロパティを使うことで、特定のHTML要素を独自のReactコンポーネントに置き換えることができます。
ここでは、「aタグ」かつ「link-cardクラスを持つ」要素を見つけたら、そのhref属性の値をLinkCardコンポーネントに渡し、そのLinkCardコンポーネントをレンダリングするように指示しています。
その後、microCMSから取得した記事本文のHTML文字列をArticleRichEditorコンポーネントに渡すことで、自動的にリンクカードが生成され、他のHTMLコンテンツと一緒に表示されます。
ページに表示させる
最後にpage.tsx
に、microCMSから取得したリッチエディタのコンテンツをArticleRichEditorコンポーネントに渡します。
import ArticleRichEditor from '@/features/article/components/ArticleRichEditor/ArticleRichEditor';
export default async function ArticleDetailPage({ params }: { params: { id: string } }) {
const article = await getArticleById(params.id); // microCMSのAPIから記事データを取得する関数
if (!article) {
return <div>記事が見つかりません。</div>;
}
return (
<div>
<h1>{article.title}</h1>
{/* microCMSのリッチエディタのコンテンツを渡す */}
<ArticleRichEditor richEditor={article.body} />
</div>
);
}
以下のように、ページにOGP情報が表示されていたら、完了です。

まとめ
この記事では、Next.jsとmicroCMS を使ってOGP 情報を取得し、実装、表示する方法を解説しました。
ぜひ、Next.jsで自分だけのリンクカードを作ってみてください。