{"componentChunkName":"component---src-pages-blog-microcms-blog-blog-id-tsx","path":"/blog/microcms-gatsby-avif/","result":{"data":{"microcmsBlog":{"blogId":"microcms-gatsby-avif","title":"microCMSのリッチエディタで貼り付けた画像をAVIF/WebPにする","date":"2022-11-02T12:00:00.000Z","body":"<p>当ブログではヘッドレスCMSとしてmicroCMSを利用しています。<br>microCMSではコンテンツの管理の他、画像管理（画像のアップデート、エンドユーザーへの配信）を行うことができます。<br></p><h2 id=\"h612d27a36d\">imgixだけど完全にimgixじゃなかったmicroCMSの画像API</h2><p>microCMSの画像APIは画像処理にimgixというサービスの機能が利用可能です。<br>imgixのAPIは画像取得時のパラメータによって様々な処理が可能で、<br>画像サイズの調整や圧縮など多様な加工をすることができます。<br><br>その中でも特に強力なのが<a href=\"https://docs.imgix.com/apis/rendering/auto/auto#format\" target=\"_blank\" rel=\"noopener noreferrer\">format=auto</a>です。これをURLパラメータにつけるだけで、</p><ul><li>受信側がAVIFに対応していればAVIFを返す</li><li>AVIFに非対応でWebPに対応していればWebPを返す</li><li>AVIFにもwebpにも非対応であれば元の拡張子のファイルを返す</li></ul><p>という挙動をしてくれます。AVIFやWebPはJPEGやPNGに比べてファイルサイズを小さくできるので、トラフィックの削減につながります。<br><br>ところが、microCMSの画像APIページを見ると、<br></p><blockquote>Information<br><code>auto=format</code>については現在非対応です。 開発検討中というステータスになります。</blockquote><p><a href=\"https://document.microcms.io/image-api/introduction\" target=\"_blank\" rel=\"noopener noreferrer\">画像APIとは</a><br><br>とのことです。がっくし。<br>ただ、自動ではなく明示的な指定は必要なものの、AVIF, WebPの取得自体は<code>fm</code>パラメータひとつの指定で行うことができます。<br><br>となれば、format=auto相当の判定をHTML側でやってあげれば、同じようなことは実現できます。<br>元のパラメータ一つ付与だけで全てやってくれる感じからは遠のいてしまいますが...<br><br>また今回は、microCMSのリッチエディタ内のHTMLに対してformat=auto相当の動作を実現させます。<br></p><h2 id=\"h34f723bf10\">リッチエディタでmicroCMSの画像を貼り付けるときにもAPIを使いたい</h2><p>microCMSでメインのコンテンツ投稿はリッチエディタ要素の中で記載することが多いと思います。<br>リッチエディタではmicroCMSの画像を簡単に貼り付けることができますが、<br>ここでは画像APIのうち幅、高さの指定しか行うことができません。できればこちらもmicroCMSにはjpeg/pngとして登録した画像であっても、AVIF/WebPで取得できるようにしたいです。<br><br>そこで今回は</p><ul><li>リッチエディタのHTMLのうち、imgタグに含まれているsrcがmicrocms.ioのサブドメインであった場合</li><li>AVIF, WebPの表示が可能であれば、それぞれAVIF, WebPを優先して取得するようにしたpictureタグを作成する</li></ul><p>ようにします。<br></p><h2 id=\"h13aac39abc\">Gatsby + microCMSの場合の実装</h2><p>自分はGatsbyでブログを作成しているので、Gatsbyでの実装例です。<br>SSRの生成処理中にmicroCMSのコンテンツを取得するので、そこで取得したリッチエディタのHTMLを強引に書き換えて出力するようにします。<br>（別にGatsby/React依存しているコードはないので任意のJavascriptベースのプロジェクトで似たようなコードで動くと思います）<br><br>ひとまず動いた感じのナイーブで実装例ですので、各々の環境に応じて変更ください。<br>HTMLパーサーのCheerioでリッチエディタのHTMLをパースし、microcmsの画像を差し替えます。<br>Cheerioが入っていない場合はプロジェクト内で</p><pre><code>yarn add cheerio</code></pre><p>などしておいてください。<br></p><pre><code>&nbsp; let body = blog.body //ブログの本文。(microCMSのリッチエディタから取得したもの)\n\n&nbsp; const cheerio = require('cheerio');\n\n&nbsp; //use avif/webp&nbsp;\n&nbsp; const contentBody = cheerio.load(body)\n&nbsp; contentBody(\"img\").each((_, elm) =&gt; {\n&nbsp; &nbsp; const src = contentBody(elm).attr(\"src\");\n&nbsp; &nbsp; //microCMSドメインでなければcontinue\n&nbsp; &nbsp; if(!src.startsWith(\"https://images.microcms-assets.io\")){\n&nbsp; &nbsp; &nbsp; &nbsp; return true;\n&nbsp; &nbsp; }\n\n&nbsp; &nbsp; //microCMSのリッチエディタのオプション設定で常にwidth, heightを返す設定をしている前提\n&nbsp; &nbsp; const width = contentBody(elm).attr(\"width\");\n&nbsp; &nbsp; const height = contentBody(elm).attr(\"height\");\n\n&nbsp; &nbsp; //make avif and webp url\n&nbsp; &nbsp; const avif_url = new URL(src)\n&nbsp; &nbsp; avif_url.searchParams.append(\"fm\", \"avif\");\n&nbsp; &nbsp; const avif_src = avif_url.toString();\n\n&nbsp; &nbsp; const webp_url = new URL(src)\n&nbsp; &nbsp; webp_url.searchParams.append(\"fm\", \"webp\");\n&nbsp; &nbsp; const webp_src = webp_url.toString();\n\n&nbsp; &nbsp; //pictureを追加してimgを削除\n&nbsp; &nbsp; //altは自分は使っていないので入れていません\n&nbsp; &nbsp; contentBody(elm).after(`&lt;picture&gt;\n&nbsp; &nbsp; &lt;source srcSet=${avif_src} type=\"image/avif\" width=${width} height=${height}&gt;\n&nbsp; &nbsp; &lt;source srcSet=${webp_src} type=\"image/webp\" width=${width} height=${height}&gt;\n&nbsp; &nbsp; &lt;img src=${src} width=${width} height=${height}&gt;\n&nbsp; &nbsp; &lt;/picture&gt;`)\n&nbsp; &nbsp; contentBody(elm).remove()\n&nbsp; });\n\n   //update body\n &nbsp;body = contentBody.html()</code></pre><p><br></p><h3 id=\"h75dfbe19c1\">効果</h3><p>試しに<a href=\"https://uskan.dev/blog/switchbot-motion-sensor/\" target=\"_blank\" rel=\"noopener noreferrer\">このページ</a>のトラフィックのうち、画像を取得している箇所を見てみます。<br></p><h4 id=\"h78f61f83d3\">実施前</h4><p><img src=\"https://images.microcms-assets.io/assets/c606031872ff4f8ab77dda96695ec0d3/44309188dffb409ebb0b83e6a53e4e5a/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%202022-11-02%2017.24.49.gif\" alt=\"\" width=\"1126\" height=\"272\"><br></p><h4 id=\"hb22c6f4e52\">実施後</h4><p><img src=\"https://images.microcms-assets.io/assets/c606031872ff4f8ab77dda96695ec0d3/52c1ddce667e47febdafaf66f6f2aa08/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%202022-11-02%2017.23.53.gif\" alt=\"\" width=\"1138\" height=\"276\"><br><br>ファイルによりますが、サイズは半分強にまで削減できました。<br></p><h2 id=\"ha214098e44\">まとめ</h2><p>今回実装したのはpictureタグでの画像出し分けだけで、<br>本質的にやっていることとしては呼び出すURLにパラメータをつけるだけです。<br>これだけトラフィックを削減できるので、imgixは大変便利です！<br><br>できればmicroCMSさんには<code>format=auto</code>を実装して、リッチエディタ内でオプション指定できるようにしてくれればもっとうれしいんですが...</p>","publishedAt":"2022-11-02T12:34:54.133Z","thumbnail":{"url":"https://images.microcms-assets.io/assets/c606031872ff4f8ab77dda96695ec0d3/bf7b1ebcffb04d13bb9e5e7a55ca95ec/blog-thumb-microcms-img.jpg"},"description":"microCMSの画像APIについておよび、リッチエディタで貼り付けた画像をavif/webp変換する実装について説明します。","tags":[{"name":"microCMS"},{"name":"Gatsby"}],"category":{"name":"ウェブ開発"}},"site":{"siteMetadata":{"title":"うすかんログ"}},"allMicrocmsCategory":{"totalCount":9,"edges":[{"node":{"categoryId":"camera","name":"カメラ","blogsCount":1}},{"node":{"categoryId":"purchase","name":"買い物","blogsCount":3}},{"node":{"categoryId":"iot","name":"IoT","blogsCount":6}},{"node":{"categoryId":"gadget","name":"ガジェット","blogsCount":0}},{"node":{"categoryId":"webdev","name":"ウェブ開発","blogsCount":8}},{"node":{"categoryId":"security","name":"セキュリティ","blogsCount":0}},{"node":{"categoryId":"bicycle","name":"自転車","blogsCount":0}},{"node":{"categoryId":"computer","name":"PC","blogsCount":0}},{"node":{"categoryId":"misc","name":"雑記","blogsCount":13}}]},"allMicrocmsTag":{"totalCount":16,"edges":[{"node":{"tagId":"tripod","name":"三脚","blogsCount":0}},{"node":{"tagId":"goods","name":"生活雑貨","blogsCount":0}},{"node":{"tagId":"shoes","name":"靴","blogsCount":1}},{"node":{"tagId":"camera-goods","name":"カメラ小物","blogsCount":0}},{"node":{"tagId":"cat","name":"ねこ","blogsCount":1}},{"node":{"tagId":"html","name":"HTML","blogsCount":1}},{"node":{"tagId":"appliance","name":"家電","blogsCount":0}},{"node":{"tagId":"chair","name":"椅子","blogsCount":1}},{"node":{"tagId":"alpha7rv","name":"α7R V","blogsCount":1}},{"node":{"tagId":"switchbot","name":"Switchbot","blogsCount":5}},{"node":{"tagId":"mui","name":"MUI","blogsCount":1}},{"node":{"tagId":"microcms","name":"microCMS","blogsCount":3}},{"node":{"tagId":"gatsby","name":"Gatsby","blogsCount":6}},{"node":{"tagId":"react","name":"React","blogsCount":4}},{"node":{"tagId":"training-log","name":"運動記録","blogsCount":9}},{"node":{"tagId":"goal","name":"目標","blogsCount":3}}]}},"pageContext":{"id":"01bb8e67-12b2-583e-a66e-f7081272b9ae","blogId":"microcms-gatsby-avif","__params":{"blogId":"microcms-gatsby-avif"}}},"staticQueryHashes":[]}