Closed21

Web Speed Hackathon 2021やってみる

nissy-devnissy-dev

ローカルでスコアを差分取りながら、自分も実装してみる。WSH_SCORING_TARGET_PATHSを適当に設定しら、スコアは上振れした。差分取りたいだけなのでこのままで

WSH_SCORING_TARGET_PATHS='["/", "/posts/01EXS42Q9GYM0WGGKJBCS3DB05", "/posts/01EXRVDZC14NQ9ERH44CHV4RC3", "/posts/01EXRK14S8HSWSFX8X7AS0DCT4", "/users/mexicandraggle", "/terms"]' yarn run scoring --id nissy-dev --url https://polar-river-66365.herokuapp.com/
yarn run v1.22.10
$ yarn workspace @web-speed-hackathon/scoring start --id nissy-dev --url https://polar-river-66365.herokuapp.com/
$ node dist/index.js --id nissy-dev --url https://polar-river-66365.herokuapp.com/
INFO [1641376792083] (9495 on pc-002024-1): Competitor: nissy-dev
INFO [1641376862411] (9495 on pc-002024-1): Score: 254.06
::set-output name=export::{"competitor":{"id":"nissy-dev","url":"https://polar-river-66365.herokuapp.com/"},"buildInfo":{"commitHash":"21b837172b90cb6b83bf10a57b4a5f81ad0d970b","buildDate":"2021-12-23T15:27:28.256Z"},"result":{"score":254.06,"lighthouseScores":{"/":{"score":11,"firstContentfulPaint":0.88,"speedIndex":0.1,"largestContentfulPaint":0,"timeToInteractive":0.05,"totalBlockingTime":0,"cumulativeLayoutShift":0.04},"/posts/01EXS42Q9GYM0WGGKJBCS3DB05":{"score":44,"firstContentfulPaint":0.88,"speedIndex":0.44,"largestContentfulPaint":0.49,"timeToInteractive":0.43,"totalBlockingTime":0.01,"cumulativeLayoutShift":0.94},"/posts/01EXRVDZC14NQ9ERH44CHV4RC3":{"score":36,"firstContentfulPaint":0.88,"speedIndex":0.39,"largestContentfulPaint":0.35,"timeToInteractive":0.03,"totalBlockingTime":0,"cumulativeLayoutShift":0.92},"/posts/01EXRK14S8HSWSFX8X7AS0DCT4":{"score":26,"firstContentfulPaint":0.88,"speedIndex":0.36,"largestContentfulPaint":0.02,"timeToInteractive":0.38,"totalBlockingTime":0.01,"cumulativeLayoutShift":0.57},"/users/mexicandraggle":{"score":48,"firstContentfulPaint":0.87,"speedIndex":0.41,"largestContentfulPaint":0.82,"timeToInteractive":0.1,"totalBlockingTime":0,"cumulativeLayoutShift":0.88},"/terms":{"score":47,"firstContentfulPaint":0.76,"speedIndex":0.47,"largestContentfulPaint":0,"timeToInteractive":0.11,"totalBlockingTime":0.61,"cumulativeLayoutShift":1}}}}
INFO [1641376866186] (9495 on pc-002024-1): buildInfo: {"commitHash":"21b837172b90cb6b83bf10a57b4a5f81ad0d970b","buildDate":"2021-12-23T15:27:28.256Z"}
✨  Done in 75.71s.
nissy-devnissy-dev

「今回での変更 (650点ライン)」までできていたこと。時間かかり過ぎだけど

  • webpackのmodeの設定
  • lodashを消す
    • なるべく素のJSで置き換えるのが良い
  • polyfill周りの変更
    • browserslist はpackage.jsonに書くほうが良い
  • momentを消す
    • dayjsもそれなりに大きいので使わなくていい場合は使わないほうが良い
  • jQueryを消す
  • CoverImageの書き換え (画像の高さ計算の削除)
    • CSSに object-fit というプロパティがあるらしい
  • tailwind cssのpurge
  • CSSのminify
  • 使っていないwebfontのcssの削除
  • AudioContextのpolyfillの削除
  • pakoの削除
  • production用のJSX変換
  • キャッシュ周り
    • APIの方は、private, no-cache or no-store の設定が良さそう
  • GIFをWebMに変換
    • ブラウザでの再生ならWebMのほうがmp4より有利っぽい
  • JPEGをWebPに変換&リサイズ
    • プログレッシブな画像のほうが実際のUXは良い場合もある
      • WebP/AVIFだとだめ、WebPについてはプログレッシブなWebP2が開発されている
    • いい記事:https://zenn.dev/gunta/articles/64de0540bafb3d
  • アイコンのバンドル
  • 画像の遅延ロード

学び

object-fit: cover; でアスペクト比を維持しつつ全体を覆うように要素に描画される。containは、画像が完全に入るように描画される
https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit

静的ファイルのCache-Controlには、immutableをつけたほうがパーフォーマンス的には良い
https://blog.jxck.io/entries/2016-07-12/cache-control-immutable.html

ブログの人は、momentやreact-helmetも素のJSに置き換えていたけど、現実的にはできないことが多そう。日付のフォーマットや計算は、dayjsでやったほうが安心感がある。react-helmetは、今回はtitleタグだけなので消せただけ。

nissy-devnissy-dev

「今回での変更 (650点ライン)」までで、できてなかったこと

  • スクロールが重いのを修正
  • ログイン状態のチェック前から画面を表示する
  • inertのpolyfillの削除
  • データ取得でのlimit/offsetの利用
  • CSSのaspect-ratioを利用する
  • CloudFlareの利用 (✋)
  • 横幅を半分にリサイズした画像も用意するように
  • normalize.cssの削除
  • 波形画像のサーバーでの生成 (✋)
  • 一方のデータが取得できたタイミングでできるだけ表示する
  • スクロールバーによるレイアウトシフトへの対応
  • font-display: swapの利用
  • 規約ページの内容の部分表示化
  • code splitting (✋)
  • モバイルでは半分のサイズの画像を利用 (✋)

ちなみに、(✋)となっているやつは自分でも思いついていたけど、実装が大変そうだったからやっていないやつ。VRTが通らない可能性が高そうだったから手をつけていなかった。

nissy-devnissy-dev

スクロールが重いのを修正

まずこれができていなかった.... なんか重いと思っていたけど、原因特定と優先順位も挙げられなかった...

原因は、(2**18回確認する)怪しいスクリプトとイベントリスナーのpassive: falseの指定。スクロール周りの実装はMutationObserverを使う機会が多いからか、まったく知らなかった。

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

これを直すと 240 → 290 くらいスコア上がった

nissy-devnissy-dev

ログイン状態のチェック前から画面を表示する

そもそもCLS結構いいのであんまり影響なさそう

inertのpolyfillの削除

これもちゃんと調べれば消せたけど、そこまでサイズも大きくないので問題なさそう

データ取得でのlimit/offsetの利用

これはちゃんとやらなきゃいけなかった...

CSSのaspect-ratioを利用する

aspect-ratioと呼ばれるプロパティがある。知らなかった...
https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio

nissy-devnissy-dev

横幅を半分にリサイズした画像も用意するように (✋)

これは意外とサクッとできるのでやればよかった

normalize.cssの削除

Tailwindにはデフォルトで https://github.com/sindresorhus/modern-normalize が入っているらしい

ここまでで 310くらいのスコア

nissy-devnissy-dev

スクロールのやつとデータ取得でのlimit/offsetの利用をやれば、300点以上にはなっていた気がする

nissy-devnissy-dev

バイナリの扱いで結構わからなくなったのでメモ

ArrayBuffer と TypedArray

ES2015で標準化されたクラス。

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

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は...?

Node.jsが独自に持っていた、バイナリデータを扱うためのクラス。 Bufferは、現在 Unit8Arrayを継承したクラスとなっており、TypedArrayに実装されているすべてのメソッドが利用可能である。slice()の挙動が違ったり、多少の互換性が無いこともある。

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

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

https://techblog.yahoo.co.jp/advent-calendar-2016/node_new_buffer/

nissy-devnissy-dev

波形データをサーバーで作るやつは、Node.js でaudioデータをデコードするライブラリが軒並みうまく使えなかった... おそらく音声をoggに変換したからな気がする....今回は波形データを作成するから、音声データはいじっちゃだめか...

以下を使ってデコードしたけど、波形データが変化してしまった... VRTでは落ちるけど、今はパフォーマンスを改善することに注力する
https://github.com/audiojs/audio-decode

ちなみに、これでスコアは 310 → 450 くらいまでアップ。これは、リソースの取得も減るし、JSの実行時間も減るので結構効くな〜 実装的には一番面倒だけど、ちゃんとやったほうが良いやつ

nissy-devnissy-dev

一方のデータが取得できたタイミングでできるだけ表示する
スクロールバーによるレイアウトシフトへの対応
規約ページの内容の部分表示化

ここらへんはCLSのための作業が多いが、CLSはかなりいいのでスキップ。

font-display: swapの利用

font-display: block だと、フォントがダウンロードされるまでは何も画面に表示されないが、font-display: swapだとフォントがダウンロードされるまでの間は代替フォントで表示される

https://laboradian.com/show-text-immediately-using-font-display/

スコアが 450 から 600 まで一気に伸びた

code splitting

JSサイズを減らすことができる

モバイルでは半分のサイズの画像を利用

これもやればスコアは上がりそうだけど、半分のサイズの画像を用意したのとほぼ同じことをやるだけなので、割愛。

nissy-devnissy-dev

「今回での変更」からは、ちょっと細かいところのチューニングになるので気になったところだけピックアップ。

fetchのpreload、サーバープッシュ
規約ページでfontのCSSをpreloadするように
preloadしていたfetchをサーバープッシュするように

TODO

preactへの移行

alias使うとかなり簡単に書き換えられるんだなー react-routerとも一緒に使えるのには驚き
https://github.com/sapphi-red/web-speed-hackathon-2021-mini/commit/6befb271100c1658bcd9beeab38bd0e773e022b7

https://preactjs.com/guide/v10/getting-started#aliasing-in-webpack

nissy-devnissy-dev

ブログの人がやっていなくて個人的にやりたかったのは、ServiceWorkerのキャッシュやuseSWRのキャッシュかな〜

useSWRのところに関しては、APIのレスポンスをCDNでにキャッシュさせているのでやっているので、近いことはやっている

nissy-devnissy-dev

結構散らかって来たので、ブログに学びをまとめて終了にする

nissy-devnissy-dev

まずは、個人ブログをCloudFlareに乗せてみた

nissy.dev のドメインのネームサーバーを、ドメインを買ったGoogle DomainからCloudflareに移行する。移行は指示通りやればできる。

ただ、移行したあとにリダイレクトループが起きたので、以下の方法で修正する必要があった。リダイレクトループが発生する流れは以下の通り。

https://support.cloudflare.com/hc/ja/articles/115000219871-リダイレクトループエラーのトラブルシューティング

blog.nissy.dev →(https) Cloudflareのエッジサーバー →(http) オリジンサーバー (Vercel) → (https) blog.nissy.dev ......

Flexible SSLに設定していると、CloudFlareがオリジンに問い合わせる時はHTTPで行うらしい。(こうしている理由って何かあるだろうか...?)オリジンサーバーの方で、HTTPをHTTPSへリダイレクトする設定が入っていると、リダイレクトループが発生する。

設定した記憶はないけど、確かに http://blog-nd-02110114.vercel.app/ にアクセスすると、自動的にhttpsにリダイレクトされる。ドキュメントにも308でリダイレクトすると書いてあった。

Furthermore, any HTTP requests to your Deployments are automatically forwarded to HTTPS using the 308 status code:

It is not possible to disable this redirection or prevent the Deployment from being served over HTTPS as it is considered an industry standard to serve web content over a sec

https://vercel.com/docs/concepts/edge-network/encryption

ちなみに今回の対応で、ドメイン全体でFull SSLとなるが、サブドメインごとに設定を変えることもできるらしい。

https://www.serversus.work/topics/yb11wi4exco4t6ujbpcu/

CloudFlareに乗せると HTTP3 でサーブされるようになった。

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

https://support.cloudflare.com/hc/ja/articles/200168076-Cloudflare-HTTP-2とHTTP-3のサポートについて#1d4DKdKMsy9CVFqr9ijrEt

VercelとCloudflareのCDNを使うことはかなりおすすめされていないのでやめた... おすすめされていない理由としては、Vercel のDeploy のタイミングでCloudfalre CDNのキャッシュをパージできないから。そもそも、キャッシュ率を見たら全然キャッシュされてなかった... CloudflareはデフォルトでJSONがキャッシュされない & 読み込んでいるファイル数的にはJSON (ブログのコンテツが入っている) が結構多いからだと予想

https://vercel.com/support/articles/using-cloudflare-with-vercel
https://www.serversus.work/topics/a8paxl3xmxl7jmjj67u3/

nissy-devnissy-dev

herokuだと色々やりづらいし、あまり使うこともないので、GAEへデプロイしてみる

https://www.cloudskillsboost.google/focuses/3340?parent=catalog

https://cloud.google.com/nodejs/getting-started

基本 stanrad 環境でデプロイするのがいいらしいが、automatic_scalingのmax_instancesやmax_idle_instancesの設定には注意する。

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

flexible 環境でやらないと、yarnコマンド実行できないらしい... 1日くらいはまった...

https://stackoverflow.com/questions/67234265/yarn-not-found-in-google-app-engine-standard-environment

https://issuetracker.google.com/issues/110097743?pli=1

おかげでデプロイの仕方とログの見方はなんとなくわかってきた

デプロイの流れ

project作成 → app 作成 → deploy
デプロイには、Cloud BuildのAPIを有効にする

ログの見方

ビルドのログ : Cloud Buildの画面からログを確認
インスタンス起動のログ:AppEngine > Services からログを確認

nissy-devnissy-dev

カスタムドメインを買って、Cloudflareに乗せてみた
スコアは620くらいから650まで変化したから、キャッシュの効果はありそう

このスクラップは2022/01/16にクローズされました