article
サーバレス構成の全体像
Amplify Gen 2 を土台に、AppSync・DynamoDB・S3・Cognito・Lambda をどう組み合わせているか。公開リクエスト、書き込み、非同期処理、キャッシュの考え方を追います。
ampless は、AWS Amplify Gen 2 を土台にしたサーバレス CMS です。管理サーバーを固定で持たず、Amplify が作るマネージドサービスを組み合わせて動かします。
ホームのアーキテクチャパネルにも、同じ考え方をレイヤーごとに分けて描いています。

レイヤー構成
| レイヤー | サービス | 担当 |
|---|---|---|
| 認証 | Cognito | 管理者・編集者のログイン、グループ制御 |
| 管理 API | AppSync GraphQL | コンテンツ CRUD、サイト設定の読み書き |
| データ | DynamoDB | 投稿、ページ、メディア、サイト設定(KvStore)、PostTag、McpToken |
| メディア | S3 | アップロード画像、静的バンドル、サイト設定キャッシュ、プラグイン公開アセット |
| プラグイン実行 | Lambda(trust_level 別) | フック、Webhook、画像処理 |
| イベント | DynamoDB Streams + SQS | 非同期フック・キャッシュ再生成 |
| 公開サイト | Next.js App Router | SSR + middleware ルーティング |
| CDN | CloudFront | キャッシュ、エッジ配信 |
これらは Amplify が CDK で生成し、構成変更も Amplify 側の流れに乗せて反映します。利用者が CDK を直接書く場面を、普段の運用からできるだけ外しているのがポイントです。
投稿のリクエスト経路
/blog-post のような公開 URL にアクセスが来たときは、だいたい次の順番で処理されます。
ブラウザ
↓ HTTPS
CloudFront(Amplify Hosting が自動構成)
↓ cache miss なら SSR
Next.js middleware
├── format: tiptap/markdown/html → テーマレンダリング
├── format: html + no_layout → /raw/<slug> に書き換え(ベア HTML)
└── format: static → /static/<slug> に書き換え(S3 へ 302)
↓
AppSync GraphQL(API キー、draft 除外カスタムリゾルバ)
↓
DynamoDB(Post / Page / Media)
middleware は、投稿の { format, metadata, updatedAt } だけを読む小さな射影を引きます。その結果を Lambda のウォームキャッシュに 60 秒だけ残し、format に応じて内部ハンドラへ振り分けます。CloudFront のキャッシュに当たれば、SSR Lambda は起動しません。
書き込みのリクエスト経路
管理 UI、MCP、公開サイトは、同じ AppSync スキーマを見ています。違うのは認証モードです。管理 UI は Cognito、MCP Lambda は IAM / SigV4、公開サイトは API キーを使います。
管理 UI (Next.js) → AppSync (Cognito User Pool: admin/editor) ─┐
MCP Lambda (HTTP) → AppSync (IAM / SigV4: allow.resource) ├→ DynamoDB / S3
公開サイト / テーマ → AppSync (API キー: draft 除外リゾルバ) ─┘
ampless パッケージ本体には、投稿 CRUD の実装を持たせていません。公開しているのは型定義、プラグイン契約、フォーマット変換ヘルパー、PostsProvider のインターフェースなどです。CRUD の実体は AppSync 側にあります。
非同期処理(DynamoDB Streams → SQS、trust_level で fan-out)
投稿公開後の副作用は、書き込みの同期処理から切り離しています。dispatcher がイベントをキューに振り分け、trust_level ごとのプロセッサ Lambda がそれぞれの IAM ロールで処理します。
DynamoDB Stream(Post + KvStore[siteconfig])
↓
event-dispatcher Lambda(イベント種別判定 + fan-out)
├── SQS: TrustedEventsQueue → processor-trusted Lambda(DynamoDB / S3 アクセス権あり)
└── SQS: UntrustedEventsQueue → processor-untrusted Lambda(外部 HTTP のみ、AWS 権限なし)
↓ リトライ 3 回失敗
EventsDlq(共有、14 日保持)
組み込みの trusted ハンドラは、いまのところ次の二つを担当します。
post.index.refresh→PostTag非正規化インデックスの差分更新site.settings.updated→public/site-settings.jsonの再生成(公開サイトはこれを 60 秒キャッシュで読む)
Webhook、RSS 再生成、OG 画像の生成のような処理は、コアに直接抱え込まず、プラグインが trusted / untrusted のどちらかの枠でイベントを購読します。
SQS を間に置くと、メッセージ単位のリトライ、DLQ 退避、流量制御を扱いやすくなります。Stream を直接処理する構成だと、1 件の失敗でバッチ全体が何度もリトライされることがあります。
1 デプロイ = 1 サイトの理由
ampless は、1 Amplify デプロイ = 1 サイトに固定しています。1 デプロイで複数サイトを Host ヘッダで振り分けるモードは入れていません。
理由はキャッシュです。Amplify Hosting の内側にある CloudFront は、キャッシュキーに Host を含めない構成になっています。複数ドメインを束ねると、SSR レスポンスを安全にキャッシュしにくくなります。サイトごとにデプロイを分ければ、この問題を避けられます。
複数サイトを並走させたい場合は、Amplify プロジェクトごと分ける運用にしてください。
なぜ Lambda@Edge / CloudFront Functions を使わないか
Amplify が自動生成する CloudFront に、カスタムエッジ関数を差し込む正規の方法は、いまのところ用意されていません。そのため、プラグイン実行はリージョン Lambda 側に寄せ、エッジでは CloudFront のキャッシュを使う方針にしています。
テキスト変換のような軽い処理なら、リージョン Lambda 側でも 1〜2 ms 程度で終わります。CloudFront のキャッシュが効いているリクエストでは、その Lambda も呼ばれません。
コストが伸び縮みする仕組み
公開ページは静的配信にかなり近い形で返します。書き込み API も、AppSync や DynamoDB などのマネージドサービスの従量課金です。月の固定費として目立つものは、AppSync の API Key 更新と、DynamoDB の最低限の保管料くらいに収まります。
金額感はホームの料金テーブルと、その下のシナリオも見てください。小さなブログなら無料枠に収まりやすく、突発的に読まれた月もサーバレスと CDN 側で受け止めます。
関連記事
- ampless の全体像 → what-is-ampless
- 投稿フォーマット → post-formats
- MCP HTTP トランスポート → mcp-http-transport