Content-Encoding: gzip を付与してコンテンツを配信する

4 min read読了の目安(約4400字

この記事では、パフォーマンス改善を試みた際に遭遇した問題をもとに、以下の流れに沿って解説していきます。

  • Content-Encoding: gzip とは
  • Firebase の圧縮技術
  • gzip圧縮をかけてS3にアップロードする
  • S3を使ってたらCloudFrontを使うと良い

[問題]: react-scripts build を改善させたい

create-react-app で作成したSPAのパフォーマンス改善をしていたのですが、ejectなどを行っていないので何から手を付けてよいか分からなかったというのが発端です。
とりあえず、公式でも推奨されてるように source-map-explorer を使用してバンドルサイズを計測し、大きな割合を占めているライブラリ等がないかを見ていました。特に目立つものはありませんでした。

https://create-react-app.dev/docs/analyzing-the-bundle-size/

CircleCIの config.yml を眺めていたら、CI上でビルドしてS3にアップロードしてCloud Frontで配信していることが分かりました。
気になったので、devtoolで見たところ Content-Encoding が設定されていません...😲

ここからgzip圧縮を有効にするまでの冒険が始まりました。

Content-Encoding とは?

そもそもの Content-Encoding についてですが
どの圧縮アルゴリズムを使用して対象のコンテンツを圧縮しているかを示すヘッダー情報です。

gzip, deflate など様々なアルゴリズムがあります。
よく見かける形式だと Content-Encoding: gzip でしょうか。

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Encoding

HTML, CSS, JS などをレスポンスとして返す際、圧縮したファイルを返すことでコンテンツサイズを減らすことでパフォーマンスを改善するという目的があります。

詳しい解説は省きますが、
CDNでWEB高速化コンテンツ圧縮(gzip)の設定と注意点 という記事を参考として置いておきます。

https://blog.redbox.ne.jp/cdn-gzip-compress.html

Firebase と圧縮技術

gzip圧縮をしてない問題に直面した際、
管理している他のサイトやアプリケーションではどのようになっているのか気になり、まずは Firebase Hosting について調べてみました。

Content-Encoding: br と書いてありました。
Googleが開発したBrotli圧縮のアルゴリズムを示しており、gzip圧縮よりも圧縮率が向上しているそうです。

firebase deploy コマンドでデプロイしているのですが、ただbuild済みのファイルをアップロードして配信するのではなく、Brotliアルゴリズムを用いて圧縮した上で Content-Encoding: br ヘッダーを付与して配信してくれてるそうです。ありがたい🙏

Chrome, Firefox, Safari, Edge でもサポートしているそうなので、IE11サポートをしていないプロダクトであれば導入しても良いかもしれませんね。

https://caniuse.com/brotli

圧縮したファイルをS3にアップロードする

ここまでで Firebase Hosting では自動でコンテンツを圧縮してくれることが分かりました。

ですが、ビルドした.js, .css ファイルをS3やGCSなどにアップロードして配信している場合、普通にアップロードするだけだと Content-Encoding ヘッダーは付与されません。

ここでは .js, .css ファイルにgzip圧縮をかけてヘッダーを付与してS3にアップロードする手順を示します。

以下の例では create-react-app のbuild済みのファイルをアップロードすることを想定しています

gzip コマンド .js.gz, .css.gz の拡張子がついたファイルを生成します。

gzip ./build/static/js/*.js ./build/static/css/*.css

圧縮したファイルの拡張子から .gz を削除

ls ./build/static/js/*.gz | sed -e s/\.gz// | awk '{print $1 ".gz " $1}' | xargs -n 2 mv
ls ./build/static/css/*.gz | sed -e s/\.gz// | awk '{print $1 ".gz " $1}' | xargs -n 2 mv

S3へ Content-Encoding, Content-Type を付与してアップロード

aws s3 sync ./build/ s3://{BUCKET_NAME} --exclude "*.css" --exclude "*.js"
aws s3 sync ./build/ s3://{BUCKET_NAME} --exclude "*.css" --include "*.js" --content-encoding "gzip" --content-type "text/javascript"
aws s3 sync ./build/ s3://{BUCKET_NAME} --exclude "*.js" --include "*.css" --content-encoding "gzip" --content-type "text/css"

これらのステップで Content-Encoding: gzip が付与され圧縮済みのコンテンツが配信されるようになります。

しかしJS, CSSだけでなくHTMLなども圧縮して配信したいとなると、コマンドを増やすのは手間です。

次のセクションでは、CloudFrontというCDNを利用した圧縮の設定について説明します。

CloudFrontで圧縮の設定をする

まずは先程のようにファイルをS3へアップロードします。
Content-Encoding などは付与しなくて大丈夫です。

aws s3 sync ./build/ s3://{BUCKET_NAME} --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers

CloudFront側の設定をします。
以下の記事を参考に、S3のバケットと対応させました。

https://qiita.com/NaokiIshimura/items/46994e67b712831c3016

CloudFront の設定が完了したら設定されるドメインにアクセスします。
コンテンツの圧縮が設定されていない場合のdevtoolの表示です。

2.4777e926.chunk.js, main.6b9f388c.chunk.js に着目します。

この状態だとgzip圧縮がされてないファイルを配信しているので、CloudFrontで圧縮の設定をしていきます。

CloudFront Distributions の画面にアクセスします。
コンテンツ圧縮の設定をしたい Distribution の ID をクリックして遷移します。

遷移したら、Behaviors タブをクリックします。

Edit 画面に遷移し赤枠で示した Compress Objects Automatically を有効にします。
これで完了です。

設定が完了したら実際に見てみると、Content-Encoding: gzip が付与されていることが分かります。

比較のために、それぞれでのコンテンツサイズを表にまとめておきました。

2.4777e926.chunk.js main.6b9f388c.chunk.js
gzip無 132 kB 1.4 kB
gzip有 43.0 kB 1.1 kB

Firebase Hostingのようにコンテンツ圧縮と配信を自動で行ってくれます。ありがたい🙏


今回は検証していませんが、GCSを使用してる場合はCloud CDNを使うことになると思います。
(Compress Objects Automatically と同じ機能はあるのかな...)

この記事では、CloudFrontを使用して手軽に Content-Encoding: gzip の付与を行うことについて説明しました。

1Byteでも削ってコンテンツ配信をする助けになればと思います。