🗜️

Nuxt3でコンテンツやアセットを圧縮配信する(gzip/brotli)

2024/09/24に公開

Nuxt 3 アプリケーションで、gzip(ジー・ジップ)や brotli(ブロトリ)を使ってコンテンツやアセットを圧縮配信する方法を調べたので、まとめてみました。ここでは、3 つのケースに分けて、具体的な設定方法を説明します。

前提

通常、コンテンツやアセットの圧縮は CDN やリバースプロキシ(例:Cloudflare や NGINX など)で行うことが一般的です。しかし特定のケースでは、アプリケーション側での圧縮が必要になることがあります。ここでは、そういった場合に活用できる方法を紹介します。

動作デモ

実際に圧縮配信が動作するデモアプリも用意したので、動かしながらこの記事を読むことで理解が深まると思います。

https://shun91-nuxt-examples.vercel.app/compression
https://github.com/shun91/nuxt-examples

圧縮の方法:3 種類

何を圧縮するかによって方法が異なります。以下では、SSR レスポンスの圧縮、静的アセットの圧縮、API プロキシでの圧縮レスポンス処理に分けて説明します。

SSR レスポンスの圧縮

SSR で生成された HTML などのレスポンスを gzip で圧縮する場合、Nitro(ナイトロ)プラグインを使用します。以下のプラグイン compression.ts は、accept-encoding ヘッダーに gzip が含まれていたらレスポンスを gzip で圧縮します。

server/plugins/compression.ts
export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('render:response', async (response, { event }) => {
    // accept-encodingにgzipが含まれている場合はtrue
    const acceptsEncoding = getRequestHeader(
      event,
      'accept-encoding',
    )?.includes('gzip');
    if (acceptsEncoding) {
      setResponseHeader(event, 'Content-Encoding', 'gzip');
      response.body = new Response(response.body).body?.pipeThrough(
        new CompressionStream('gzip'),
      );
    }
  });
});

このプラグインを追加した後、アプリケーションを起動してブラウザで確認すると、gzip 圧縮が適用されたレスポンスが返されることがわかります。

ブラウザのキャプチャ

ちなみに、上記ソースコード内で使用しているrender:responseは、SSR でのページレンダリング時に呼び出される Nitro フックです。

アセットの圧縮

次に、JavaScript や CSS などの静的アセットを圧縮する方法です。これを行うには、nuxt.config.tsに以下の設定を追加します。これにより、nuxt build コマンドによるビルド時にアセットが自動で gzip や brotli 形式で圧縮されます。

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    compressPublicAssets: true,
  },
});

この設定を有効にすると、ビルド後に生成される .output/public フォルダ内のファイルが圧縮されます。以下は圧縮の有無を比較した結果です。

compressPublicAssets: true compressPublicAssets: false
compressPublicAssets: true compressPublicAssets: false

アプリケーションを起動してブラウザで確認すると、圧縮されたアセットが配信されていることがわかります。

ブラウザのキャプチャ

なお、開発モード(nuxt dev)ではこの圧縮設定は適用されないので、開発中に圧縮を確認するためにはビルド後に生成されたファイルをチェックしてください。

API プロキシでの圧縮レスポンス処理

最後に、API プロキシを経由して圧縮レスポンスを保持する場合の処理です。API プロキシを使って外部 API にリクエストを行い、そのレスポンスがすでに圧縮されている場合、プロキシでレスポンスの圧縮が保持されるようにする設定が必要です。これを実現するために、http-proxy-middleware を使います。

以下は、プロキシの設定例です。Nitro のミドルウェアとして設定します。

server/middleware/proxy.ts
import { createProxyMiddleware } from 'http-proxy-middleware';

export default defineEventHandler(async (event) => {
  const proxyMiddleware = createProxyMiddleware({
    target: process.env.API_SERVER_URL, // プロキシ先のURL
    changeOrigin: true,
    pathFilter: ['/api'], // プロキシ対象のパス
    pathRewrite: { '^/api': '' }, // 任意:パスの書き換え
    on: {
      proxyReq: (proxyReq) => {
        // 任意:Request Header が溢れる場合は不要なcookieを削除する
        proxyReq.removeHeader('cookie');
        // 任意:認証情報を付与。ここではIdTokenだが必要なものを付与するとよい
        const idToken = getCookie(event, 'id_token');
        if (idToken) {
          proxyReq.setHeader('Authorization', `Bearer ${idToken}`);
        }
      },
    },
  });

  await new Promise((resolve, reject) => {
    proxyMiddleware(event.node.req, event.node.res, (err) => {
      if (err) reject(err);
      else resolve(true);
    });
  });
});

上記のミドルウェア設定を適用すると、API リクエストに対して圧縮が有効なレスポンスを保持できます。例えば、process.env.API_SERVER_URLhttps://dummyjson.com を設定して http://localhost:3000/api/todos を開くと、次のように圧縮が適用されたレスポンスが返されることがわかります。

ブラウザのキャプチャ

API プロキシの設定に関する詳細は、http-proxy-middleware の公式リポジトリを確認してください。

圧縮の効果:70~90%のサイズ削減

以下の表のように、圧縮処理をすることで、テキストベースのデータ(HTML、CSS、JavaScript など)は 70%から 90%のサイズ削減が期待できます。

alt text
テキストベースのアセットのエンコードと転送サイズを最適化する  |  Articles  |  web.devより引用

これにより、ネットワークを介して転送されるデータ量が削減され、結果としてページの表示速度の改善が期待できます。

圧縮の注意点:サーバーリソースの消費

アプリケーションのサーバー側での圧縮処理は CPU リソースを消費します。通常は無視できるほどの負荷で済むはずですが、トラフィックが多い場合やサーバーのリソースが限られている場合には注意が必要です。必要に応じて負荷検証を行っておくと安心です。

まとめ

Nuxt 3 では、gzip や brotli によるコンテンツやアセットの圧縮を比較的簡単に導入できます。CDN やリバースプロキシでの圧縮ができない場合には、ぜひ試してみてください。

参考資料

GitHubで編集を提案

Discussion