nissy.dev

Web Speed Hackathon 2021 mini での学び

16 min read
  • #blog
目次

Web Speed Hackathon 2021 mini に参加していたのですが、こちらのほぼ満点を出した人の記事を元に、自分でも色々やってみた時に学習したことをまとめます。

こちらのスクラップでまとめていたのですが、色々興味のある内容があって話が脱線することも多かったので、再度自分のためにまとめ直した記事です。

学んだこと

CSS のプロパティ

object-fit

縦横比を維持、中央配置、トリミング、以上がすべて自動で行われる object-fit: cover をよく使う気がする。背景画像については、似たような設定が background-size でできることは知っていたが、画像については知らなかった。

object-fit CSS property - CSS | MDN
The object-fit CSS property sets how the content of a replaced element, such as an <img> or <video>, should be resized to fit its container.
developer.mozilla.org

aspect-ratio

名前の通り、要素の縦横のアスペクト比を定義できる。aspect-ratioは、レイアウトシフトを防ぐことにも効果を発揮することがある。

Flexbox の場合、要素のコンテンツによって幅や高さが決定することがあり、レイアウトシフトの原因になっている。予め、aspect-ratio で要素の幅と高さを決めることで、レイアウトシフトを防ぐことが可能。

aspect-ratio CSS property - CSS | MDN
The aspect-ratio CSS property allows you to define the desired width-to-height ratio of an element's box.
developer.mozilla.org

画像のファイル形式

WebP にすべきかと思っていたけど、より高画質でサイズの小さい AVIF と呼ばれる形式が最近は開発されている。(サポートブラウザは、こちらを参考)

さらに、ユーザの UX を考えると、プログレッシブな画像のほうが好ましいい場合も多い。プログレッシブな画像というのは、徐々にレンダリングされる画像のこと。PNG や JPEG はプログレッシブだが、WebP はプログレッシブではない。このため、プログレッシブに対応した WebP2 も開発されている。以下のブログの内容は、非常に勉強になった。

https://zenn.dev/gunta/articles/64de0540bafb3d

動画のファイル形式

ブラウザでの再生なら WebM のフォーマットが良い。ソースはあまり見つけられなかったが、Google が積極的にブラウザに有利なフォーマットとして開発しているらしい。昨年夏に Safari が対応したので、Chrome、Safari、Firefox などの主要なブラウザでは扱う事ができる。

Passive Event Listeners

スクロールに関するイベントに登録するリスナーは、以下のように第 3 引数に passive: true を渡すことで、パフォーマンスを改善できる。

document.addEventListener("touchstart", onTouchStart, { passive: true });

jxck さんのブログに、この機能が解決する問題が細かく解説されている。

Scroll Event にイベントリスナが登録された場合、そのイベントリスナの中で preventDefault() が呼ばれる場合は、スクロールを止めなくてはいけない。

ところが、登録されたイベントリスナの中で preventDefault() が実行されるかどうかは、実際にイベントリスナを実行してみないとわからない。

つまりブラウザは、 Scroll Event にイベントリスナが登録されている場合、ハンドラの実行が完了し preventDefault() が呼ばれなかったことを確認してからでないと、スクロールすることができない。

https://blog.jxck.io/entries/2016-06-09/passive-event-listeners.html

Use passive listeners to improve scrolling performance  |  Lighthouse  |  Chrome for Developers
Learn how to improve your page&#39;s scrolling responsiveness by avoiding passive event listeners.
web.dev

JS でのバイナリデータの扱い方

ArrayBuffer と TypedArray

ArrayBuffer は、バイナリデータを扱うための基底クラスで、バイナリデータを格納する箱 (領域) のイメージ。TypedArray (Unit8Array, Uint16Array など) は、ArrayBuffer を操作するためのクラス。TypedArray からしかバイナリデータを追加、削除することはできない。どちらも ES2015 で標準化されたクラス。

const buf = new ArrayBuffer(8);
console.log(buf); // ArrayBuffer { byteLength: 8 }
buf[0] = 0; // エラーは出ないけど、値として反映されない

const ua = new Uint8Array(buf);
console.log(ua); // Uint8Array [ 0, 0, 0, 0, 0, 0, 0, 0 ]
ua[0] = 1;
console.log(ua); // Uint8Array [ 1, 0, 0, 0, 0, 0, 0, 0 ]

Buffer

Buffer は、ES2015 以前から Node.js が独自に実装していたバイナリデータを扱うためのクラス。 現在は、Unit8Array を継承したクラスとなっており、TypedArray に実装されているすべてのメソッドが利用可能である。

https://nodejs.org/api/buffer.html#buffers-and-typedarrays

以下のように、一部互換性がないところもある。

Buffer#slice の実装は、既存の Buffer のコピーなしで作成するのに対し、TypedArray#slice の実装はコピーを作成するため、動作に違いが出ます。

今年のうちに対応したい、Node.jsのBufferに潜む危険性
Node.js v6.x と v4.x で共に非推奨になったnew Buffer()に関する脆弱性と、LTS(v4.5.0,v6.9.x系)に追加されたBuffer.from,Buffer.alloc,Buffer.allocUnsafeとzero-fill-buffersオプションの紹介です。
techblog.yahoo.co.jp

font-display の設定

読み込みを早くするには、font-display: swap の方が良い。

Lazy loading

ファーストビューに表示されている画像まで Lazy loading してしまうと、LCP のスコアが悪くなるので注意する。

また画像だけではなく、画面外で要素の描画をスキップできる content-visibility も存在する。細かい使い方については、以下のブログが参考になる。

表示速度を飛躍的に向上させるHTML/CSS最新仕様「content-visibility」「Lazy loading」「contain」をコード付き簡単解説
要素の読み込みや描画を簡単に調整して表示速度改善ができる、「content-visibility」「Lazy loading」「contain」を紹介します。
techblog.yahoo.co.jp

キャッシュのおさらい

キャッシュの流れ

  1. レスポンスを保存する
  2. 保存したレスポンスを再利用する

キャッシュの種類

キャッシュの設定

個人情報などを扱うページについては、local キャッシュのみを許可する private を設定する。どこにもキャッシュされたくない場合は、no-store を設定する。(no-cache ではない!!)

ちなみに、なにも Cache-Control を設定しない場合、レスポンスはどこかでキャッシュされる可能性があるので、キャッシュされたくない値を使うときは注意する。

キャッシュのライフサイクル周りの設定

max-age=0 は、キャッシュが無効にはならず、Request Collapsing の場合にキャッシュが返ってしまうことに注意する。

CDN からオリジンへのリクエストの処理中に、同じ URL に対してリクエストが発生すると、最初のレスポンスを待って、2 つ目以降のリクエストにも同じレスポンスが返される仕様になっていました。

CDNの使い方 in Mercari/CDN in Mercari
CDN in Mercari/CDNの使い方 in Mercari Fastly Yamagoya Meetup 2017
speakerdeck.com

must-revalidate は、一見 no-cache と似ているけど、キャッシュが stale かどうかで挙動が異なる。

stale-while-revalidate

キャッシュが stale になったら、指定された期間内は stale なキャッシュを返しつつ、バックグラウンドでキャッシュを更新する設定。

「キャッシュは効かせたいが、なるべく新鮮なリソースを提供したい。」などといった要望に対処する

https://blog.jxck.io/entries/2016-04-16/stale-while-revalidate.html

immutable

キャッシュが有効期限内であればリロード時もキャッシュを再利用させる設定。ブラウザがリロードする際は、キャッシュが fresh (有効期限内) か stale かどうかに関わらず、条件付きリクエストを発行し、キャッシュの検証を行う。

https://blog.jxck.io/entries/2016-07-12/cache-control-immutable.html

条件付きリクエスト

no-cachemust-revalidate の設定のときに行われている、キャッシュの再利用時にサーバーにキャッシュの有効性を問い合わせるリクエストを条件付きリクエストと呼ぶ。

有効性の評価については、リソースが変更された最終時刻のタイムスタンプかリソースのハッシュ値を利用する。リソースが変更された最終時刻のタイムスタンプの場合は、最初のリソース取得のときにサーバーが Last-Modified でタイムスタンプをブラウザに返し、送られた Last-Modified を使って If-Modified-Since ヘッダを付与した条件付きリクエストをサーバーに送信する。サーバーでは、タイムスタンプの時刻をつかってキャッシュが有効がどうか判断する。リソースのハッシュ値の場合は、最初のリソース取得のときにサーバーが Etag でタイムスタンプをブラウザに返し、送られた Etag を使って If-Non-Match ヘッダを付与した条件付きリクエストをサーバーに送信する。サーバーでは、ハッシュ値をつかってキャッシュが有効がどうか判断する。

キャッシュ設定の基本

まとめると、以下のフローチャートを元に設定するのが良さそう。

Evita solicitudes de red innecesarias con la caché HTTP  |  Articles  |  web.dev
La caché HTTP del navegador es tu primera línea de defensa contra solicitudes de red innecesarias.
web.dev

Cloudflare の基礎

Cloudflare の CDN を利用する場合は、対象サイトのドメインのネームサーバーを Cloudflare が管理するネームサーバに変更し、プロキシの設定を行うことで利用できる。

試しにこのブログを Cloudflare に乗せて見たところ、リダイレクトループが起こったりしたものの、CDN から配信することができた。コンテンツは、こちらのドキュメントの理由から HTTP3 で配信されるようになった。

ブラウザと Web サーバーが利用可能な最高位のプロトコルを自動的にネゴシエートします。そのため、HTTP/3 は HTTP/2 よりも優先されます。Cloudflare が HTTP/1.x を使うのは、オリジン Web サーバーと Cloudflare の間だけです。

キャシュヒットしているかどうかは、レスポンスヘッダの CF-Cache-Status の値などをみればいいらしい。デフォルトでキャッシュするファイルの一覧は以下のリンクから確認できる。HTML はデフォルトでキャッシュされない。

Default Cache Behavior · Cloudflare Cache (CDN) docs
Default file extensions and content types that Cloudflare caches.
developers.cloudflare.com

ちなみに、リダイレクトループが起きた原因は、エッジサーバーからオリジンサーバーへのリクエストにはデフォルトで HTTP を利用し、オリジンが HTTP を HTTPS にリダイレクトするためだった。(結局、Vercel では Cloudflare を使うことが推奨されてなかったのですぐにやめた。)

Cloudflare はドメイン移管も行っていて、卸売価格でドメインを管理できる。(.dev は移管できなかった...)

Google App Engine の基礎

Google App Engine でのデプロイは、デプロイに必要な app.yaml を作成し、基本的に以下の 3 ステップで行える。

// プロジェクトの作成
$ gcloud projects create test-project --set-as-default

// App Engine の作成
$ gcloud app create --project=test-project

// デプロイ
$ gcloud app deploy

App Engine は、Standard 環境と Flexible 環境を提供している。違いや注意すべき点については、以下のブログに丁寧に書かれている。特に、Standard 環境の自動スケーリングの設定については、小さくするのを忘れないようにしたい。

https://zenn.dev/catnose99/articles/f99ea2a8b985b2

今回は、Standard 環境で yarn コマンドを実行させることができずに 1 日位ハマってしまった... サンプルなどを見ても、基本的には Flexible が推奨されている感じがするので、もしデプロイするなら はじめは Flexible を試すのが良いかもしれない。

nodejs-docs-samples/appengine at d0e24cecafa33174fe5ba56f1ffa12c746ff539e · GoogleCloudPlatform/nodejs-docs-samples
Node.js samples for Google Cloud Platform products. - GoogleCloudPlatform/nodejs-docs-samples
github.com

感想