📣

Amplify HostingのAdvancedな機能を深掘ってみる(画像最適化もできるよ)

2024/10/29に公開

はじめに

こんにちは!
犬専用の音楽アプリ オトとりっぷでエンジニアしています、足立です!

この記事では、Advanced な Amplify Hosting を覗き、その裏側を学んでいく内容になっています。「Amplify Hosting の裏側ってどうなってるの?」と気になっている方の手助けになれば幸いです。

Advanced な機能の概要

独自の SSR 可能なフレームワークをデプロイする

Amplify Hosting には、Advanced な機能(以下 Advanced Hosting )というものが存在します。

https://docs.aws.amazon.com/ja_jp/amplify/latest/userguide/ssr-deployment-specification.html

フレームワークの作成者は、ファイルシステムベースのデプロイ仕様を使用して、特定のフレームワーク用にカスタマイズされたオープンソースビルドアダプターを開発できます。これらのアダプターは、アプリケーションのビルド出力を、Amplify ホスティングの想定されるディレクトリ構造に準拠するデプロイバンドルに変換します。このデプロイバンドルには、ルーティングルールといったランタイム設定など、アプリケーションをホストするために必要なすべてのファイルとアセットが含まれます。

要するに、「どんなフレームワークでも Amplify Hosting 用にカスタマイズすることが可能ですよ」ってことです。この機能が開放されたおかげで自由に SSR フレームワークをデプロイすることが可能になりました。

この機能を使った先人たちの例はこちらです。

https://www.m3tech.blog/entry/amplify-astro-ssr

https://gensobunya-tech.hatenablog.com/entry/2023/11/21/221416

手前味噌ですが、Remix もあります。

https://zenn.dev/ototrip/articles/tech-remix-amplify-1

SSR 可能なフレームワークを自分でカスタマイズして Amplify Hosting にデプロイすることが可能になった訳ですが、実はこれらのデプロイ仕様を眺めていると、Amplify Hosting の裏側が透けて見えてきます。

Amplify Hosting のアーキテクチャ

Amplify Hosting のアーキテクチャを説明する公式資料はそこまで多く存在しませんが、公式ブログに記載されているものとして、こちらの図があります。


(上記ブログより)

Amplify Managed Frontend の構成要素として、Compute リソースは AWS Lambda、Static リソースは S3、CDN リソースは Amazon CloudFront と説明されていますね。
例えば Next.js を利用すると、ユーザー側は設定を気にせずに自動的にリソースが配置されます。逆に Advanced Hosting は、これらの設定をある程度自由に書き換えられるということです。

Advanced Hosting デプロイ仕様

詳細なドキュメントはこちらにあります。
詳しい説明は公式に譲るとして、簡単には ComputeStaticルーティングルールが必要になります。

Compute

まずコンピューティングリソースですが、以下のような構造が必要です。

.amplify-hosting/
├── compute/
│   └── default/
│       ├── node_modules/
│       └── index.js
...
└── deploy-manifest.json

index ファイルは、例えば Hono を使った場合以下の通りです。

index.ts
import { serve } from '@hono/node-server';
import { Hono } from 'hono';

const app = new Hono();

app.get('/', (c) => {
  return c.text('Hello, Amplify!');
});

serve(app);

node-server モードです。これは恐らく、Lambda のイメージがAWS Lambda Web Adapterだからだと考えられます。

https://github.com/awslabs/aws-lambda-web-adapter

これのおかげで、Lambda 上にデプロイされていることを気にしなくていいわけですね。

そして、deploy-manifest.jsonに Compute モードを記載します。

deploy-manifest.json
{
  "routes": [
    {
      "path": "/*",
      "target": {
        "kind": "Compute",
        "src": "default"
      }
    }
  ],
  "computeResources": [
    {
      "name": "default",
      "runtime": "nodejs20.x",
      "entrypoint": "index.js"
    }
  ],
}

この場合は、すべてのパスを Compute モードにルーティングさせると言う意味になります。

Static

静的アセットには、以下のような構造が必要です。

.amplify-hosting/
...
├── static/
│   ├── favicon.ico
│
...
└── deploy-manifest.json

static 以下に画像ファイルなどを置けばいいわけですね。
そして、deploy-manifest.jsonに Static モードを記載します。

deploy-manifest.json
{
  "routes": [
    {
      "path": "/*.*",
      "target": {
        "kind": "Static"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*",
      "target": {
        "kind": "Compute",
        "src": "default"
      }
    }
  ],
  ...
}

この場合は、.を含むすべてのパス(つまり拡張子を持つもの)を Static モードにルーティングさせると言う意味になります。
routes は上から順に適応されていきますので、.を含むすべてのパスは Compute よりも Static のパスが優先されます。

ImageOptimization

Advanced な機能として忘れてはいけないのが画像最適化です。
これは例えば、「特定の画像ファイルを最適化しておきたい」のようなニーズを満たします。

どういうことかと言うと、

├── static/
│   ├── images/
│   │   └── test.png
...
└── deploy-manifest.json

というtest.png画像ファイルを静的に配信したいのだけど、「利用する画像サイズがあらかじめ分かっており、さらにいい感じに WebP などに変換しておいてほしい」のような場合に、deploy-manifest.jsonに以下のように追記します。

deploy-manifest.json
{
  "imageSettings": {
    "sizes": [100, 200],
    "domains": [],
    "remotePatterns": [],
    "formats": ["image/webp", "image/png", "image/jpeg"],
    "minimumCacheTTL": 60,
    "dangerouslyAllowSVG": false
  },
  "routes": [
    {
      "path": "/images/*",
      "target": {
        "kind": "ImageOptimization",
        "cacheControl": "public, max-age=31536000, immutable"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*.*",
      "target": {
        "kind": "Static"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    ...
  ]
  ...
}

imageSettingsformatsの加筆によって、images 以下のすべての画像ファイルが 100, 200 のサイズを持つ WebP および JPEG 形式に変換されます。(変換はビルドが完了してデプロイ時に実行されていると思われます。デプロイ時間が大幅に増えますので、そう推測してます。)

https://main.<AppID>.amplifyapp.com/images/test.pngというルーティングは、Static モードだけであればそのままtest.png画像が返却される訳ですが、
先ほどの説明の通り上位からルールが適応されますので、Static モードではなく ImageOptimization モードへと振り分けられます。

また、ImageOptimization にアクセスするには、クエリパラメータが必須となります。逆に images 以下にはクエリパラメータなしにアクセスできなくなりますのでご注意ください。クエリパラメータは例を表記しておきます。詳しくは公式ドキュメントをご確認ください。

クエリパラメーター タイプ 必須 説明
url string 可能 ソースイメージ URL への相対パスまたは絶対パス。 ?url=https%3A%2F%2Fwww.example.com%2Fbuffalo.png
width number 可能 最適化された画像の幅 (ピクセル)。 ?width=800

まとめると、このようなリクエストになると思います。
https://main.<AppID>.amplifyapp.com/images/?url=/images/test.png&width=100

実際にアクセスすると、content-type: image/webpと WebP 形式の 100px にリサイズされた画像が取得できます。

最終的には

以下のような構造になると思います。

.amplify-hosting/
├── compute/
│   └── default/
│       ├── node_modules/
│       └── index.js
├── static/
│   ├── images/
│   │   └── test.png
│   └── favicon.ico
└── deploy-manifest.json
deploy-manifest.json
{
  "version": 1,
  "imageSettings": {
    "sizes": [100, 200],
    "domains": [],
    "remotePatterns": [],
    "formats": ["image/webp", "image/png", "image/jpeg"],
    "minimumCacheTTL": 60,
    "dangerouslyAllowSVG": false
  },
  "routes": [
    {
      "path": "/images/*",
      "target": {
        "kind": "ImageOptimization",
        "cacheControl": "public, max-age=31536000, immutable"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*.*",
      "target": {
        "kind": "Static"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*",
      "target": {
        "kind": "Compute",
        "src": "default"
      }
    }
  ],
  "computeResources": [
    {
      "name": "default",
      "runtime": "nodejs20.x",
      "entrypoint": "index.js"
    }
  ],
  "framework": {
    "name": "demo",
    "version": "0.0.1"
  }
}

ちなみに

Amazon CloudFront と AWS Lambda を使用した画像の最適化という公式ブログが存在します。
動的に画像のリサイジングしたいだけであれば、こっちを利用した方がいいですね。

最後に

いかがでしたでしょうか。

もし犬専用の音楽アプリに興味を持っていただけたら、ぜひダウンロードしてみてください!

https://www.oto-trip.com/

Discussion