🏃‍♂️

Vite + CloudFront で gzip されたファイルを配信する方法

2023/08/11に公開

はじめに

Vite + React のアプリケーションを S3 + CloudFront でホスティングしています。この構成でビルド後の成果物を gzip して配信する方法はいくつかあると思います。本記事ではそれらの方法を実際に試し、どの方法を採用するのが良さそうか検討してみます。

gzip とは

gzip については MDN の説明が分かりやすいです。

gzip はファイルの圧縮および展開に使われるファイル形式です。これは Deflate アルゴリズムに基づいており、ファイルをより小さくすることができ、より高速なネットワーク転送を可能にします。gzip はウェブサーバーや最近のブラウザーが広く対応しており、サーバーは送信前に自動的にファイルを圧縮し、ブラウザーは受信時に展開することができます。

https://developer.mozilla.org/ja/docs/Glossary/GZip_compression

ビルド後の成果物をレスポンスとして返す時に、gzip されたファイルを返すことでコンテンツのサイズを減らすことができ、結果的にレスポンスタイムを短くできます。

gzip されていることを確認するには?

レスポンスヘッダーに Content-Encoding: gzip が含まれていれば gzip されています。

Content-Encoding ヘッダーはどの圧縮アルゴリズムが使われているかを示していて、Chrome を使っている場合は、Devtools の Network パネルから確認できます。

ただ、これだと1つずつ開いて確認する必要があるので、Content-Encoding のカラムを追加すると楽です。(カラム部分を右クリックすると表示するカラムが選べます)

様々な方法で gzip を試す

yarn create vite したアプリのビルド結果を S3 にアップロードして動作確認します。
今回はビルド後の成果物に含まれる index-xxx.css で比較していきます。

gzip なし

まずは、gzip なしの場合です。
Content-Encoding カラムを見ると空になっていますね。
Size は 1.8KB、Time は 69ms となっています。

CloudFront で gzip する

CloudFront の自動圧縮を有効にすることで、gzip されるようになります。
具体的には、該当のディストリビューションを選択 → ビヘイビアを編集 → 『オブジェクトを自動的に圧縮』の設定を Yes に変更します。

CloudFront のキャッシュを削除して再度確認してみます。

すると、今度は Content-Encoding カラムに gzip と表示されていますね。
Size は 1.1KB、Time は 22ms となりました。
gzip 前と比べると、Size・Time ともに小さくなり、Time に関しては 3/1 ほどになったのでかなり効果がありそうです。

CloudFront の自動圧縮の詳細が知りたい場合はこちらをご確認ください。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html

余談ですが、Terraform の場合は compress オプションを指定することで自動圧縮を有効にできます。

Vite のプラグイン + S3 へのアップロード時にメタ情報を付与する

次はビルド時に gzip する方法です。
後ほど細かく見ていきますが、ざっくり次のような流れです。

  1. vite-plugin-compression2 を使ってビルド時に gzip する
  2. S3 へのアップロード時にメタ情報を付与する

vite-plugin-compression2 を使ってビルド時に gzip する

vite-plugin-compression2 はビルド後の成果物を gzip するためのプラグインです。
https://github.com/nonzzz/vite-plugin-compression

まずはインストールします。

yarn add vite-plugin-compression2 -D

次は Vite の設定ファイルに vite-plugin-compression2 を組み込みます。

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
+ import vitePluginCompression from 'vite-plugin-compression2'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
+   vitePluginCompression({
+     filename: '[path][base]',
+     deleteOriginalAssets: true
+   }),
  ],
})

ここで注目していただきたいのは、filenamedeleteOriginalAssets です。
デフォルトの場合、gzip すると元のファイルとは別で .gz の拡張子付きのファイルが作られます。
S3 + CloudFront の構成の場合、.gz 付きのファイルを返す方法がない(もしあればコメントで教えていただけると助かります🙏)ため、gzip されていないファイルを返してしまいます。

そのため、少し強引ですが gzip 後に元のファイル名で上書きしています。
ただ、gzip 済みのファイルに .gz がついていない状態になので、あまり良い方法ではないかもしれないです...

S3 へのアップロード時にメタ情報を付与する

事前に gzip したファイルをアップロードするため、先述の CloudFront 側での自動圧縮を有効している場合、2重で gzip されてしまいます。この問題の回避方法はCloudFront のドキュメントに記載されています。

オブジェクトが圧縮されるようにオリジンがすでに設定されている
オブジェクトが圧縮されるように CloudFront を設定し、オリジンでもオブジェクトが圧縮される場合、オリジンにはオブジェクトがすでに圧縮されていることを CloudFront に示す Content-Encoding ヘッダーが含まれている必要があります。オリジンからのレスポンスに Content-Encoding ヘッダーが含まれる場合、ヘッダーの値に関係なく CloudFront ではオブジェクトを圧縮しません。CloudFront ではレスポンスをビューワーに送信し、エッジロケーションでオブジェクトをキャッシュします。

注目していただきたいのは オリジンからのレスポンスに Content-Encoding ヘッダーが含まれる場合、ヘッダーの値に関係なく CloudFront ではオブジェクトを圧縮しません の部分です。
S3 + CloudFront の構成の場合、オリジンは S3 になるので、S3 へのアップロード時に Content-Encoding ヘッダーを追加できれば良さそうです。これは S3 へのアップロード時にメタデータを追加することで実現できます。

メタ情報を付与した状態で S3 にアップロードした後、CloudFront のキャッシュを削除して再度確認してみます。

まずは、Content-Encoding ヘッダーですが、gzip と記載されていますね。
Size は 1.1KB、Time は 95ms となりました。
gzip 前と比べると、Size は小さくなっているものの、Time は逆に長くなってしまいました。

結論

これまで検証した結果をまとめると次のようになりました。

gzipなし CloudFront の自動圧縮 vite-plugin-compression2
Size 1.8KB 1.1KB 1.1KB
Time 69ms 22ms 95ms

結論、今回比較した中で最も効果があったのは、CloudFront の自動圧縮機能を使う方法でした。また、この方法の場合、設定を1つ変えるだけで有効にできるため、導入のハードルも低そうだと思いました。他にもっと効率の良い方法があればコメントで教えていただけると助かります!

参考

Discussion