Amplify HostingのAdvancedな機能を深掘ってみる(画像最適化もできるよ)
はじめに
こんにちは!
犬専用の音楽アプリ オトとりっぷでエンジニアしています、足立です!
この記事では、Advanced な Amplify Hosting を覗き、その裏側を学んでいく内容になっています。「Amplify Hosting の裏側ってどうなってるの?」と気になっている方の手助けになれば幸いです。
Advanced な機能の概要
独自の SSR 可能なフレームワークをデプロイする
Amplify Hosting には、Advanced な機能(以下 Advanced Hosting )というものが存在します。
フレームワークの作成者は、ファイルシステムベースのデプロイ仕様を使用して、特定のフレームワーク用にカスタマイズされたオープンソースビルドアダプターを開発できます。これらのアダプターは、アプリケーションのビルド出力を、Amplify ホスティングの想定されるディレクトリ構造に準拠するデプロイバンドルに変換します。このデプロイバンドルには、ルーティングルールといったランタイム設定など、アプリケーションをホストするために必要なすべてのファイルとアセットが含まれます。
要するに、「どんなフレームワークでも Amplify Hosting 用にカスタマイズすることが可能ですよ」ってことです。この機能が開放されたおかげで自由に SSR フレームワークをデプロイすることが可能になりました。
この機能を使った先人たちの例はこちらです。
手前味噌ですが、Remix もあります。
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 デプロイ仕様
詳細なドキュメントはこちらにあります。
詳しい説明は公式に譲るとして、簡単には Compute
とStatic
のルーティングルール
が必要になります。
Compute
まずコンピューティングリソースですが、以下のような構造が必要です。
.amplify-hosting/
├── compute/
│ └── default/
│ ├── node_modules/
│ └── index.js
...
└── deploy-manifest.json
index ファイルは、例えば Hono を使った場合以下の通りです。
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
だからだと考えられます。
これのおかげで、Lambda 上にデプロイされていることを気にしなくていいわけですね。
そして、deploy-manifest.json
に Compute モードを記載します。
{
"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 モードを記載します。
{
"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
に以下のように追記します。
{
"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"
}
},
...
]
...
}
imageSettings
のformats
の加筆によって、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
{
"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 を使用した画像の最適化という公式ブログが存在します。
動的に画像のリサイジングしたいだけであれば、こっちを利用した方がいいですね。
最後に
いかがでしたでしょうか。
もし犬専用の音楽アプリに興味を持っていただけたら、ぜひダウンロードしてみてください!
Discussion