このWEBサイトはTwitterにリンクを張るだけで自動でサムネイルが表示されるようになっている。
本来ならば次のように表示されるはず。
しかし、ここ1ヶ月以内に表示されなくなっていた。
今回はコレを解決したので調査した順に記録しておく。
この記事はGatsby.js
で作られているサイト向けとなるのでワードプレスなどのサイトでは効果がないと思われるので予めご了承いただければと思う。
詳しくは解説しないが、このカードを表示したい場合はmeta
タグに必要な情報を記述するだけで良い。
Gatsby.js
に関しても難しく考える必要はなくhelmet
の中に記述してあればよい。
次のサイトでカードが表示できるかを確認できる(公式サイト)
Card Validator | Twitter Developers
しかし、何度やってもサムネイルが表示されず、LOGにERROR: No card found (Card error)
という表示が出ている。
どうやらmeta
タグがTwitterに認識されていない様子。
一応ブラウザのデベロッパーモードで確認すると間違いなくタグは入っている。
以前は正しくカードが表示されいたのでエラーになる理由がわからない。
また、タグ周りのコードも書き換えた記憶がない。
ピンポイントで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
上のスレッドをたどっていくと、どうやら本来実装されているはずである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タグが書き込まれていないことに気づく。
gatsby-plugin-react-helmet
の挙動ここまで調査して初めて知ったのだが、そもそもgatsby-plugin-react-helmet
というプラグインが入っていれば自動でhtml
にヘッダを書き込んでレンダリングしてくれるそうだ。
こちらのプラグインは既にインストール済みだったので、コレまでのコードは無意味だったことに気づく。
ここで偶然にも解決の糸口となる気付きがあった。
ここ最近追加したコードに「クライアントのブラウザは モバイル? PC?」という判別ができるまでレンダリングしない」というコードがあることを思い出した。
つまり読み込んだ直後に「ブラウザ環境で処理が切り替わる」というコードが入っている。
もしかしてこの「条件式」があることによってhelmet
タグがサーバーレンダリングされずスクリプトでクライアントサイドで書き込まれているようになっているのではないかと考えた
これがかなり偶然で「大正解」だったようだ。
つまりhelmetが入っているコンポーネントは条件式の外で書かなければならないようだ
例えば私のサイトでは次のレイアウトコンポーネント内のchildren
の中にhelmet
が入っていたので外に出しただけだ。
{!isFirstEnter &&
<Main open={mobileOpen}>
{children}
</Main>
}
※isFirstEnter
という条件が邪魔をしている例
こう書くと、ページごとにタイトルや解説、画像といったmeta
タグに変数を使うようなコードが作れなくなるのでは?と思われるかもしれない。
実際には(あくまで私のケースでは)このisFirstEnter
の条件のすぐ上にhelmet
を移動しただけなので、コードも殆ど変わっていない。
helmet
がページごとに切り替わる挙動のままカード化が成功している。
さて、たったこれだけの実装でmeta
タグはbuild
後のhtml
にダイレクトに書き込まれており、タグの位置も上の方に来ている。
この状態でデプロイして試したところ正しくカードが表示された。
まとめると helmetのタグが含まれているコンポーネントが条件式で分岐しているとbuild時にmetaタグがハードコードされない ということを覚えておいてもらえればと思う。
このためTwitterのBOTが認識できていなかったと考える。
またFacebookのカードが表示されない不具合も同じような理由で解決できる可能性があるので試してみてほしい。
かなりピンポイントな問題解決になったが役に立つ人がいるかもしれないので記録として残した。