2022-01-21

【Gatsby.js】突然Twitter Cardが表示されなくなったので対処する

GatsbyサイトにてTwitter側でmetaタグが正しく認識されず、Twitter Cardが表示されない不具合を解決

Article Image

Twitterにカードが表示されない問題

このWEBサイトはTwitterにリンクを張るだけで自動でサムネイルが表示されるようになっている。

本来ならば次のように表示されるはず。

表示サンプル

しかし、ここ1ヶ月以内に表示されなくなっていた。

今回はコレを解決したので調査した順に記録しておく。

本サイトはGatsby.js(React)製のサイトである

この記事はGatsby.jsで作られているサイト向けとなるのでワードプレスなどのサイトでは効果がないと思われるので予めご了承いただければと思う。

本来カードはmetaタグを設定するだけでできる

詳しくは解説しないが、このカードを表示したい場合はmetaタグに必要な情報を記述するだけで良い。

Gatsby.jsに関しても難しく考える必要はなくhelmetの中に記述してあればよい。

Twitterカードが表示されるか確認する

次のサイトでカードが表示できるかを確認できる(公式サイト)

Card Validator | Twitter Developers

しかし、何度やってもサムネイルが表示されず、LOGにERROR: No card found (Card error)という表示が出ている。

metaタグが読まれていない

どうやらmetaタグがTwitterに認識されていない様子。

一応ブラウザのデベロッパーモードで確認すると間違いなくタグは入っている。

以前は正しくカードが表示されいたのでエラーになる理由がわからない。

また、タグ周りのコードも書き換えた記憶がない。

調査1: metaタグが下に行き過ぎている

ピンポイントでGatsby.jsのWEBサイトの問題を解決されているサイトを発見。

今回の調査は全てはこちらのサイトを起点にして調査することとなった。

Gatsby.jsでTwitter Cardが表示されなくて苦悩した件

※この場を借りて感謝致します。

問題はどうやらTwitterのBOTが先頭数キロバイトしか読まないらしくmetaタグが後ろに行くと認識されないとのこと。

この方法ではタグの入れ替えを行いmetaタグを優先的に上に来るようにソートしている。

解決せず

このサイトを始めGitHubの元スレッドも読み、色々テストを行うも残念ながら解決せず。

しかし、ここ数日前まで議論されていた別スレッドを発見。Gatsby.js側にバグがあったのではと考えた。

gatsby-plugin-react-helmet orders the components too late in · Issue #22206 · gatsbyjs/gatsby

調査2: gatsby.js側のバグか?

上のスレッドをたどっていくと、どうやら本来実装されているはずであるbuild時のタグを自動整列する機能が正しく動作していなかった模様。

既にfixが実装されているようなのでnpm updateを行う。

さらにスレッドに貼られているコードをいくつか試した。次はその一例。

gatsby-ssr.jsに次を記述

const React = require("react")
const { Helmet } = require("react-helmet")

exports.onRenderBody = (
  { setHeadComponents, setHtmlAttributes, setBodyAttributes },
  pluginOptions
) => {
  const helmet = Helmet.renderStatic()
  setHtmlAttributes(helmet.htmlAttributes.toComponent())
  setBodyAttributes(helmet.bodyAttributes.toComponent())
  setHeadComponents([
    helmet.title.toComponent(),
    helmet.link.toComponent(),
    helmet.meta.toComponent(),
    helmet.noscript.toComponent(),
    helmet.script.toComponent(),
    helmet.style.toComponent(),
  ])
}

exports.onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
  const headComponents = getHeadComponents()

  headComponents.sort((x, y) => {
    if (x.props && x.props["data-react-helmet"]) {
      return -1
    } else if (y.props && y.props["data-react-helmet"]) {
      return 1
    }
    return 0
  })

  replaceHeadComponents(headComponents)
}

こちらはmetaタグのソートとonRenderBodyを使ってhelmetで書き込まれるタグをサーバーサイドレンダリング(ビルド時にhtmlファイルに書き込んでしまう)という処理を行っていると思われる。

解決せず

こちらも全く効果がなかった。

ここで気づいたのがbuildで生成されるhtmlのコードを見てもmetaタグが書き込まれていないことに気づく

調査3: gatsby-plugin-react-helmetの挙動

ここまで調査して初めて知ったのだが、そもそもgatsby-plugin-react-helmetというプラグインが入っていれば自動でhtmlにヘッダを書き込んでレンダリングしてくれるそうだ。

こちらのプラグインは既にインストール済みだったので、コレまでのコードは無意味だったことに気づく。

クライアント側でmetaタグが書き込まれている?

ここで偶然にも解決の糸口となる気付きがあった。

ここ最近追加したコードに「クライアントのブラウザは モバイル? PC?」という判別ができるまでレンダリングしない」というコードがあることを思い出した。

つまり読み込んだ直後に「ブラウザ環境で処理が切り替わる」というコードが入っている。

もしかしてこの「条件式」があることによってhelmetタグがサーバーレンダリングされずスクリプトでクライアントサイドで書き込まれているようになっているのではないかと考えた

正解だった

これがかなり偶然で「大正解」だったようだ。

つまりhelmetが入っているコンポーネントは条件式の外で書かなければならないようだ

例えば私のサイトでは次のレイアウトコンポーネント内のchildrenの中にhelmetが入っていたので外に出しただけだ。

    {!isFirstEnter &&
    <Main open={mobileOpen}>
        {children}
    </Main>
    }

isFirstEnterという条件が邪魔をしている例

余談:helmetを移動するとタグに変数が使えなくなるかも?

こう書くと、ページごとにタイトルや解説、画像といったmetaタグに変数を使うようなコードが作れなくなるのでは?と思われるかもしれない。

実際には(あくまで私のケースでは)このisFirstEnterの条件のすぐ上にhelmetを移動しただけなので、コードも殆ど変わっていない。

helmetがページごとに切り替わる挙動のままカード化が成功している。

metaタグが書き込まれ上に移動した

さて、たったこれだけの実装でmetaタグはbuild後のhtmlにダイレクトに書き込まれており、タグの位置も上の方に来ている。

この状態でデプロイして試したところ正しくカードが表示された。

今回のまとめ

まとめると helmetのタグが含まれているコンポーネントが条件式で分岐しているとbuild時にmetaタグがハードコードされない ということを覚えておいてもらえればと思う。

このためTwitterのBOTが認識できていなかったと考える。

またFacebookのカードが表示されない不具合も同じような理由で解決できる可能性があるので試してみてほしい。

かなりピンポイントな問題解決になったが役に立つ人がいるかもしれないので記録として残した。



この記事のタグ

この記事をシェア


謎の技術研究部 (謎技研)