0

%

Knowledge

【WordPressヘッドレス化】OGP情報を入れたオリジナルのリンクカードを作ってみた

コーディング

こんにちは!アルテガエンジニアのHarukichiです。

最近、弊社のコーポレートサイトをWordPressヘッドレス化 x Astroでリニューアルしました。
その時に右往左往したことをシリーズに分けてアウトプットしています。

今回は、オリジナルのリンクカードを作成したお話です。

経緯

実現したかったこと
GraphQLで取得されたスタンダードなURLから、OGP情報を入れたオリジナルのリンクカードを表示させたい!

旧WordPressサイトでは「Pz-LinkCard」プラグインを使用して、ベタ打ちしたURLをリンクカードとして表示していました。
残念ながらWPGraphQLの互換性はなく、ヘッドレス化で取得したときにシンプルなHTMLになってしまいました( ; ; )
↓こんな感じ

これを、以下のOGP情報を含めたオリジナルのカードにしていきます。

  • タイトル
  • サムネイル
  • URL
  • ディスクリプション

前提として、この部分はクライアントサイドで動くものを作りました。
Astro静的ビルドの時に生成するのが最善だと思ってはいましたが(外部APIに依存しているので安定性としては劣るかもしれない)
ビルドの時間をこれ以上増やしたくなかったための選択です。

WPGraphQLで取得したデータ

以下の2つのケースを対応させました。

①Pz-LinkCardプラグインを使用していたときのURL


<p><em>SALONIA 公式ブランドサイト</em><br>\n<a href="\"https://salonia.jp/\"" rel="\"noopener\"" target="\"_blank\"">https://salonia.jp/</a></p>

②今後記事を書くときのコード


<p>https://arutega.jp/</p>

∟今後は、ベタ打ちでURLを書いたら自動的にカードになるようにしました。

必要なURLのみをカードに変換するjsを作成

除外する条件

必要ないものまでカードにしないようにしました。

  • style属性がついている
  • blog-buttonクラスを持つ
  • imgやvideoタグを含む
  • p>em>aタグの構造
  • a.text !== a.hrefなリンク

JSコード

// link-card.js


export default function initLinkCard() {
  const container = document.getElementById('post-body');
  if (!container) return;

  Array.from(container.querySelectorAll('p')).forEach(async (p) => {
    // 除外①: style属性あり
    if (p.hasAttribute('style')) return;

    // 除外②: aタグが1つでなければ対象外
    const links = p.getElementsByTagName('a');
    if (links.length !== 1) return;

    const a = links[0];

    // 除外③: blog-buttonクラス
    if (a.classList.contains('blog-button')) return;

    // 除外④: imgまたはvideoタグを含む
    if (p.querySelector('img, video')) return;

    // 除外⑤: p>em>a構造
    if (a.parentElement?.tagName === 'EM' && a.parentElement.parentElement === p) return;

    // 除外⑥: テキストがhrefと異なる
    if (a.textContent.trim() !== a.href) return;

    const href = a.href;

    try {
      const res = await fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(href));
      if (!res.ok) throw new Error(`Network error: ${res.status}`);

      const html = await res.text();
      const doc = new DOMParser().parseFromString(html, 'text/html');

      const ogTitle = doc.querySelector('meta[property="og:title"]')?.content || href;
      const ogDesc = doc.querySelector('meta[property="og:description"]')?.content || '';
      const ogImg = doc.querySelector('meta[property="og:image"]')?.content || '/images/common/no-img.svg';

      const card = document.createElement('div');
      card.className = 'link-card';
      card.innerHTML = `
        <a href="${href}" target="_blank" rel="noopener noreferrer">
          <div class="link-card__thumb">
            <img decoding="async" src="${ogImg}" alt="${ogTitle}" loading="lazy">
          </div>
          <div class="link-card__body">
            <h4 class="link-card__title">${ogTitle}</h4>
            <small class="link-card__url">${new URL(href).hostname}</small>
            ${ogDesc ? `<p class="link-card__desc">${ogDesc}</p>` : ''}
          </div>
        </a>
      `;

      p.replaceWith(card);
    } catch (e) {
      console.warn('リンクカード変換エラー:', href, e);
    }
  });
}

OGP情報を取得する部分

MITライセンスの「AllOrigins」を使用させていただきました。

①指定したURLの情報をAPI経由で取得


const res = await fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(href));

②取得したHTMLはDOMParserを使ってパースし、OGPタグを抽出


const html = await res.text();
const doc = new DOMParser().parseFromString(html, 'text/html');

const ogTitle = doc.querySelector('meta[property="og:title"]')?.content || href;
const ogDesc  = doc.querySelector('meta[property="og:description"]')?.content || '';
const ogImg   = doc.querySelector('meta[property="og:image"]')?.content || '/images/common/no-img.svg';

記事ページで適用する

先ほどの変換用のjsを読み込み、クライアントサイドで動かすようにします。


<script>
  import initLinkCard from '../../libs/link-card.js';
  initLinkCard();
</script>

CSSスタイルは省略しますが、こんな感じのオリジナルカードができました!↓

https://arutega.jp/case/niwa-houzing/

まとめ

今回は、旧サイトにPz-LinkCardプラグインを使っていた時のURLが大量にあったので、
クライアントサイドでOGPカード表示ができるように変換する方法を考えました。
外部のAPIサービスに依存しているため、ご参考の際は自己責任でお願いいたします。
また、もっといい方法があればぜひ教えてください🙇‍♀️

※なお、外部サイトのOGP情報の取得に関しては、ウェブサイト側の仕様変更やアクセス制限、利用規約等により、正しく情報が取得できなくなる可能性があります。運用には十分ご注意ください。