JSでツイートを埋め込むときのベストプラクティス

5 min read読了の目安(約5000字 2

Webページでのツイートの埋め込みについて、パフォーマンス周りで色々と工夫できることが分かったのでまとめておきます。

いちばん基本的なツイートの埋め込み方

最も基本的な埋め込みの仕方は、ツイートのメニューから[ツイートを埋め込む]を選んで、出力されたHTMLを貼り付けることです。

具体的には以下のようなHTMLが出力されます。

<blockquote class="twitter-tweet">
  <p lang="ja" dir="ltr">Zennは個人開発を脱却してクラスメソッド社のサービスとなりました。<br>僕は引き続きZennの開発を続けます。<br><br>🐥 ZennとClassmethod<a href="https://t.co/jpes7Kbdpz">https://t.co/jpes7Kbdpz</a></p>&mdash; catnose (@catnose99) <a href="https://twitter.com/catnose99/status/1356048923983400964?ref_src=twsrc%5Etfw">February 1, 2021</a>
</blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

これをページに貼り付けると、<blockquote><iframe>置き替わり、よく見る形でツイートが表示されます。

HTMLの変換はplatform.twitter.com/widgets.jsにより行われます。

JSフレームワークでツイートを埋め込む

しかし、ReactやVueなどのJavaScriptフレームワークでは、コンポーネント内でそのまま埋め込みコードを貼り付けても、<script>が実行されないためツイートは表示されません。

これだとツイートは表示されない
function SomeComponent() {
  return (
    <div>
      <blockquote class="twitter-tweet">...</blockquote>
      <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
    </div>
  )
}

そのため、widgets.jsをコンポーネントのマウント後に読み込むことになります。例えばReactであれば以下のようにuseEffectを使った書き方になるでしょう。

ツイートが表示される
 function SomeComponent() {
+ useEffect(()=>{
+     // scriptを読み込み
+     const script = document.createElement('script');
+     script.src = "https://platform.twitter.com/widgets.js";
+     document.body.appendChild(script);
+     // アンマウント時に一応scriptタグを消しておく
+     return () => {
+       document.body.removeChild(script);
+     }
+  }, [])
 
   return (
     <div>
      <blockquote class="twitter-tweet">...</blockquote>
 -     <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
     </div>
   )
 }

複数のツイートを埋め込む

厄介なのはツイートが埋め込まれるコンポーネントが複数こ配置されるようなケースです。コンポーネントごとにマウント時にwidgets.jsが読み込まれてしまいます。ブラウザにしばらくキャッシュされるとはいえ、同一の<script>がページにどんどん追加されていくのはあまり気持ちが良いものではありません。

また、widgets.jsの処理ではdocument.bodyから埋め込み対象となる要素(twitter-tweetクラスを持つblockquote要素)を探します。コンポーネントごとにわざわざbody全体を読みに行ってしまうのは非効率です。

2つめ以降の埋め込みに対しては、widgets.jsによりグローバルに定義されるtwttr.widgets.load(埋め込み対象となる要素)を実行すれば多少効率は上がりますが、widgets.jsがすでに読み込み済みかどうかを判定したりするのが面倒だったりします。

https://zenn.dev/catnose99/articles/28cfc8de6dd15c59353c

公式ドキュメントで紹介されているスニペットを活用する

コンポーネントごとにツイートが埋め込まれるようなケースでは、公式ドキュメントで「best performance and reliability」と紹介されているスニペットを用いるのが良さそうです。

https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/guides/set-up-twitter-for-websites
<script>window.twttr = (function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0],
    t = window.twttr || {};
  if (d.getElementById(id)) return t;
  js = d.createElement(s);
  js.id = id;
  js.src = "https://platform.twitter.com/widgets.js";
  fjs.parentNode.insertBefore(js, fjs);

  t._e = [];
  t.ready = function(f) {
    t._e.push(f);
  };

  return t;
}(document, "script", "twitter-wjs"));</script>

The above snippet optimizes loading by:

  1. Assign a HTML element ID of twitter-wjs to easily identify if the JavaScript file already exists on the page. Exit early if the ID already exists.
  2. Asynchronously load the Twitter for Websites JavaScript.
  3. Initialize an asynchronous function queue to hold dependent functions until the script is available.

Include this snippet before any other JavaScript on your page which may depend on the twttr.ready asynchronous function queue.

ものすごくざっくりとまとめると、

  • scriptはまだページに存在しないときにだけ非同期に読み込まれるよ(すでに存在する場合は読み込まれない)
  • scriptが読み込みが完了するまで埋め込みを行うfunctionの実行を待機するよ
  • サイトが複数の埋め込みをおこなっている場合は、一度に設定を完了することができて、サイトの高速化につながるよ

という感じですね。

このスニペットを読み込むだけではツイートの埋め込み自体は行われず、twttr.widgets.load()twttr.widgets.createTweet()を呼び出す必要があります。ただ、widgets.jsの読み込みがいい感じに行なわれるようになるため、コンポーネントごとの実装が楽になります。

具体的な実装例

上述のスニペットをアプリ全体もしくはページの<head>などに追加しておき、各コンポーネントでは要素のツイートへの変換を行うtwttr.widgets.load()を実行するのが効率的と言えそうです。

ツイートの埋め込み先などまで細かく調整したい場合はload()の代わりにcreateTweet()を使うのが良いと思います。詳しくは
Embedded Tweet JavaScript Factory Functionを参考にしてください。

twttr.widgets.loadの引数に要素を指定すると、その子要素の中からツイートの埋め込み対象となる要素(blockquote.twitter-tweet)を探してくれます。

例えばReactなら以下のような感じで書くと、<blockquote class="twitter-tweet">の部分がツイートに置き換えられます。

Example.jsx
function Example() {
  const containerElem = useRef(null); // コンポーネントのルートとなる要素を取得

  useEffect(()=>{
    twttr.widgets.load(containerElem); // ツイートの埋め込みを実行
  }, [])
 
  return (
    <div class={containerElem}>
      <blockquote class="twitter-tweet">...</blockquote>
    </div>
  )
}

ReactであればuseEmbeddedTweetみたいなカスタムフックを作っても良いかもしれません。

これならパフォーマンス面で優れているだけでなく、コンポーネントごとの実装が楽になります。SPAなどで複数箇所にツイートが埋め込まれるような場合にはぜひ試してみてください。