Share
Embed
AmplifyHostingConstructからSSRフレームワークのためのホスティング設計を考察する/amplify-hosting-construct
MURAKAMI Masahiko
July 18, 2026
Transcript
24 slides
-
AmplifyHostingConstructからSSRフレームワークのた めのホスティング設計を考察する AWS CDK Conference Japan 2026 presented by JAWS-UG
2026-07-18 株式会社永和システムマネジメント プリンシパルエンジニア 村上 雅彦 a.k.a @fossamagna AWS CDK Conference Japan 2026 presented by JAWS-UG 1 -
自己紹介 名前: 村上 雅彦 所属: 株式会社永和システムマネジメント コミュニティ活動: Amplify Japan User
Group 運営メンバー JAWS-UG 福井支部 運営メンバー AWS Community Builder (DevTools since 2022) X (Twitter): @fossamagna GitHub: @fossamagna AWS CDK Conference Japan 2026 presented by JAWS-UG 2 -
このセッションで話すこと Amplify Gen2で開発中のSSRホスティング機能(L3 Construct)の実装コードから、 3つの設計判断 を深掘りします。 1. 複数フレームワーク対応のL3、 「インターフェース」を何にするか 2.
CloudFront OAC × Lambda Function URLのPOST 403問題 3. CloudFrontのルーティング設計、そしてその進化 各テーマは「問題 → あなたならどうする? → 実際の設計判断」の順で進めます。 ぜひ自分ならどう設計するかを考えながら聞いてください。 AWS CDK Conference Japan 2026 presented by JAWS-UG 3 -
題材はAmplify Gen2の「Self-Managed Hosting」 RFC: aws-amplify/amplify-backend#3211(Developer Preview) // amplify/hosting.ts import {
defineHosting } from '@aws-amplify/hosting'; defineHosting({ framework: 'nextjs' }); Next.js / Nuxt / Astro / SPA などを自分のAWSアカウントにデプロイ CloudFront・Lambda・API Gateway・S3・WAFv2・Route53 を組み合わせたSSRホスティン グ defineHosting() / definePipeline() 、そして 独立したL3 Construct としても使える import { AmplifyHostingConstruct } from '@aws-amplify/hosting/constructs'; new AmplifyHostingConstruct(stack, 'Hosting', props); AWS CDK Conference Japan 2026 presented by JAWS-UG 4 -
CFP提出後、実装がaws-blocksへ移管された CFP提出時点では aws-amplify/amplify-backend リポジトリ内で実装 現在は aws-devtools-labs/aws-blocks の @aws-blocks/hosting が本体 PR
#3246(2026-06-18): リポジトリ内にあった実装 約28,000行を削除 して、 Constructの実装一式を @aws-blocks/hosting から import する形に PR #3264(2026-07-08): pipeline も @aws-blocks/pipeline の薄いシムに // @aws-amplify/hosting は再エクスポート(エイリアス) export { HostingConstruct as AmplifyHostingConstruct } from '@aws-blocks/hosting'; AWS CDK Conference Japan 2026 presented by JAWS-UG 5 -
移管後はAmplify固有のグルーコードだけが残った @aws-amplify/hosting(Amplify固有のグルーコード) ├── defineHosting() … デプロイロック、backend identifier、outputs保存 ├── definePipeline() …
amplify/hosting.ts の自動検出・実行 └── 再エクスポート … AmplifyHostingConstruct など │ import ▼ @aws-blocks/hosting(実装本体 = 今日読むコード) └── HostingConstruct (L3) … Storage / Compute / Cdn / Waf / Dns / Monitoring AWS Blocks(IfCフレームワーク)の Hosting ブロックと 同じ実装を共有 汎用部分はaws-blocksへ、Amplify統合だけが @aws-amplify/hosting に残った AWS CDK Conference Japan 2026 presented by JAWS-UG 6 -
全体アーキテクチャ Next.js Nuxt Astro SPA … ← ⼊⼒は多様 ❓❓❓ ←
ここに何を置く?(設計判断①) HostingConstruct (L3) StorageConstruct S3(静的アセット) ComputeConstruct Lambda(SSR / 画像最適化) CdnConstruct CloudFront + API Gateway WafConstruct WAFv2 DnsConstruct ACM + Route 53 ← 出⼒は共通の AWSリソース群 AWS CDK Conference Japan 2026 presented by JAWS-UG 7 -
設計判断① あなたならどうする? 「Next.js・Nuxt・Astro・SPA…複数のフレームワークに対応するL3 Constructを作りたい」 L3のpropsを、あなたならどう設計しますか? A: framework: 'nextjs' を受け取り、L3の中でフレームワークごとに分岐する B:
NextjsHosting / NuxtHosting … フレームワークごとに別のConstructを作る C: フレームワーク非依存の中間表現を定義し、L3はそれだけを見る AWS CDK Conference Japan 2026 presented by JAWS-UG 8 -
設計判断① 答えはC、 の正体は DeployManifest Next.js Nuxt Astro SPA … ←
⼊⼒は多様 アダプターが変換 DeployManifest ← フレームワーク⾮依存の「契約」 HostingConstruct (L3) StorageConstruct S3(静的アセット) ComputeConstruct Lambda(SSR / 画像最適化) CdnConstruct CloudFront + API Gateway WafConstruct WAFv2 DnsConstruct ACM + Route 53 ← 出⼒は共通の AWSリソース群 L3はNext.jsを知らない。見るのはこの「契約」だけ AWS CDK Conference Japan 2026 presented by JAWS-UG 9 -
設計判断① 「契約」にすると何が良いのか 「設定」: framework: 'nextjs' のような、L3が解釈するオプションの集まり。 対応フレームワークが増えるたびに L3本体の改修が必要 になる 「契約」:
渡す側(アダプター)と受け取る側(L3)が 事前に合意した仕様。 zodスキーマで検証され、契約さえ満たせば誰でもL3への入力を作れる const myAdapter: FrameworkAdapterFn = (projectDir): DeployManifest => ({ version: 1, compute: {}, // SSR用Lambdaの定義 staticAssets: { directory: path.join(projectDir, 'dist') }, routes: [{ pattern: '/*', target: 'static' }], // ルーティング規則 }); defineHosting({ customAdapter: myAdapter }); フレームワークの知識はアダプター側に隔離。新対応はアダプターを1つ足すだけで、 L3は変更不要( customAdapter で自作も可能) L3の入力は「設定」ではなく「契約」として設計する AWS CDK Conference Japan 2026 presented by JAWS-UG 10 -
設計判断② GETは動くのに、POSTだけ403 SSRのLambdaをCloudFrontの後ろに置きたい。素直な構成は CloudFront + OAC → Lambda Function URL(署名付きでオリジンを保護)
ところが… GET / → 200 POST /api/submit → 403 SignatureDoesNotMatch フォーム送信・Server Actions・APIへのPOSTがすべて失敗する AWS CDK Conference Japan 2026 presented by JAWS-UG 11 -
なぜ壊れるのか、CloudFrontはボディに署名できない CloudFront + OAC ボディはバッファせず流し込むだけ → ハッシュを署名に含められない Lambda Function URL
未署名ペイロードを受け付けない (IAM認証の仕様) POST(ボディ + 署名) ボディは 未署名 のまま送る 署名済みの ボディハッシュを要求 ✕ 403 SignatureDoesNotMatch 署名は ボディより先に送るヘッダー に載る。ストリーミング転送のCloudFrontは ボディ全体のハッシュを計算できず、未署名のまま送る(AWSドキュメント明記の制限) 回避策の x-amz-content-sha256 はクライアントが自分でハッシュを計算して送る方式 → ブラウザからのリクエストでは制御不能 GET(ボディなし)は影響なし。 「一見動く」からタチが悪い AWS CDK Conference Japan 2026 presented by JAWS-UG 12 -
設計判断② あなたならどうする? SSRの要件として、ストリーミング応答・大きなボディ(ファイルアップロード) ・低レイテンシ は諦めたくない A: OACの署名をやめる( Signing.NEVER ) B:
Lambda@Edgeでボディのハッシュを計算し、 x-amz-content-sha256 を注入して 署名を成立させる(SSTが採用している方式) C: Function URLをやめてAPI Gatewayを間に挟む AWS CDK Conference Japan 2026 presented by JAWS-UG 13 -
設計判断② 実際は3段階の試行錯誤だった コミット履歴に、3段階の試行錯誤がそのまま残っている 試行 アプローチ 結果 1 OAC署名オフ( Signing.NEVER )
Function URLに直アクセス可能。セキュリティ破綻で却下 2 API Gateway HTTP API ストリーミング非対応。SSRの要件を満たせず却下 採用 API Gateway REST API + STREAM ResponseTransferMode.STREAM (2025年11月の新機能)で解決 // cdn_construct.ts: SSR LambdaはAPI Gateway経由に const integration = new LambdaIntegration(ssrFn, { proxy: true, responseTransferMode: ResponseTransferMode.STREAM, }); AWS CDK Conference Japan 2026 presented by JAWS-UG 14 -
設計判断② 採用された構成 CloudFront + WAFv2 + KVSルーター 動的(POST含む) Referer: シークレットを付与
API Gateway REST API STREAM・binaryMediaTypes ['*/*'] SSR Lambda POST・ストリーミング ✓ 画像(GETのみ) Function URL + OAC署名 ボディなしのGETなら署名問題を踏まない 画像最適化 Lambda ※Nuxt IPXはAPI GW経由に(7/15〜) 静的アセット S3 + OAC builds/{buildId}/ プレフィックス 全部をAPI Gateway経由にはしない。リクエストの性質でオリジンを使い分ける AWS CDK Conference Japan 2026 presented by JAWS-UG 15 -
設計判断② 採用構成を実コードで見る const restApi = new RestApi(this, 'SsrRestApi', { endpointTypes:
[EndpointType.REGIONAL], // CloudFrontが前段に居るのでedge-optimizedは二重プロキシ binaryMediaTypes: ['*/*'], // ないとボディがbase64二重エンコードされ壊れる policy: /* Refererに「決定的シークレット」がなければDENY */ }); binaryMediaTypes: ['*/*'] がバイナリのアップロード/ダウンロード/ストリーミングを守 る CloudFrontがRefererヘッダーにシークレットを付与し、 それを持たないAPI Gatewayへの直アクセスはリソースポリシーで403に GETのみの画像LambdaはOAC + Function URL。 ただしNuxt IPXは 別のSigV4問題 でAPI GW経由に(2026-07-15) AWS CDK Conference Japan 2026 presented by JAWS-UG 16 -
同じ問題への別解、SSTのLambda@Edge署名方式 SST: Lambda@Edgeで署名 本実装: API GW REST + STREAM リクエストボディ上限
1MB(Lambda@Edgeの制限) 10MB ストリーミング ボディ全体をメモリに載せてハッシュ計算 STREAMでネイティブ対応 Lambda@Edgeクォータ 消費する(上限に注意) 消費しない 追加レイテンシ/コスト 全動的リクエストでLambda@Edge起動 API GWリクエスト料金 保守 独自の署名グルーコードを保守 マネージド機能に委譲 どちらも「正解」 。前提と優先順位が違うだけ AWS CDK Conference Japan 2026 presented by JAWS-UG 17 -
気になったので、RFCで開発チームに聞いてみた Issue #3211 で「Lambda@Edge方式は検討したか?」と質問すると、メンテナーから丁寧な回答 が 仮説(1MB制限・メモリバッファリング)は「正しい」 。ただし理由はそれだけではない 全動的リクエストへの追加コストとレイテンシ Lambda@Edgeの制約(us-east-1限定・伝播が遅い・サイズ/ランタイム制限) 独自SigV4署名コードを保守し続けるリスク
API Gatewayならスロットリング・リクエスト検証・WAF・アクセスログが無償で付く 逆に「あなたのユースケースは?」と聞かれ、議論が発展 → マルチテナント構成でのメリット(Lambdaテナント分離)に気づけた OSSなのでコードから設計判断を読み、Issueで「なぜ」を聞ける AWS CDK Conference Japan 2026 presented by JAWS-UG 18 -
設計判断③ CloudFrontのルーティング、どう並べる? マニフェストにはリテラルもワイルドカードも混在したルートが並ぶ /api/edge/special ← リテラル /api/edge/* ← ワイルドカード /_next/static/*
/* CloudFrontのビヘイビアは「先着一致」 。並べ方を間違えると /api/edge/special が /api/edge/* に食われる あなたなら、この並び順をどう決めますか? AWS CDK Conference Japan 2026 presented by JAWS-UG 19 -
設計判断③ スコアリングで機械的に順序を決める // kvs_router.ts: リテラルセグメント数が支配し、同数なら長い方が先 export const routeSpecificity = (pattern:
string): number => { const literalSegments = pattern .split('/') .filter((s) => s !== '' && s !== '*').length; return literalSegments * 1000 + pattern.length; }; /api/edge/special (3×1000+17)> /api/edge/* (2×1000+11)> /* (0×1000+2) 具体的なパターンほど必ず先に来る。並び順を人間の注意力ではなくコードで保証する AWS CDK Conference Japan 2026 presented by JAWS-UG 20 -
設計判断③ そして設計はKVSエッジルーターへ進化した さらに調べていくと、ビヘイビアを並べる方式は すでに過去のもの になっていた 旧: ルート = ビヘイビア(インフラ) CloudFront
Distribution /api/edge/special → Lambda /api/edge/* → Lambda /_next/static/* → S3 …ルートの数だけビヘイビアが増える 上限 75個 / Distribution ルートの変更 = インフラ(Distribution)の更新 進化 新: ルート = KVSのデータ CloudFront Distribution ビヘイビアは 1個 だけ CloudFront Function → cf.selectRequestOriginById() ルートテーブルを参照 KeyValueStore(ルートテーブル = データ) /api/edge/special→server /_next/static/*→s3 … 上限 5MB(データ) ルートの変更 = KVSのデータ更新のみ(デプロイも⾼速) インフラだったものをデータに変えると、上限から自由になる ( routeSpecificity はKVSルートテーブルの走査順として現役) AWS CDK Conference Japan 2026 presented by JAWS-UG 21 -
今日、紹介しきれなかった工夫たち 工夫 内容 L2自動生成物の差し替え L2が裏で作る CfnPermission を tryRemoveChild() で除去して作り直す 決定的シークレット
乱数でなくSHA-256導出にしてCloudFormationの無駄な差分を防ぐ アトミックデプロイ builds/{buildId}/ プレフィックス + prune: false でゼロダウンタイム Token.isUnresolved() 環境非依存スタックでもsynthを壊さないバリデーション ログバケットの罠 CloudFrontアクセスログは BUCKET_OWNER_ENFORCED だと無音で止まる ビルド成果物のパッチング OpenNext/Nitroの出力をAPI GW v1ペイロード対応に書き換え Skew Protection デプロイ直後の新旧ビルド混在をcookie+KVSで回避 クォータ予算ガード KVS 5MB・CloudFront Function サイズをsynth時に検査 → 興味があれば aws-devtools-labs/aws-blocks の packages/hosting を読んでみてください AWS CDK Conference Japan 2026 presented by JAWS-UG 22 -
まとめ、L3 Constructの実装から学んだ設計判断 1. L3の入力は「設定」ではなく「契約」として設計する(DeployManifest) 2. 制約が変わればベストな構成も変わる (REST+STREAMという新機能が解を変えた。SSTとの比較で前提の違いが見えた) 3. インフラだったものをデータに変えると上限から自由になる(KVSエッジルーター) そして、いちばん伝えたかったのはこれです
コードから設計判断を読み、Issueで「なぜ」を聞くと、OSSは最高の教材になる みなさんも「自分ならこう設計する」をぜひ考えてみてください。よかったら教えてください AWS CDK Conference Japan 2026 presented by JAWS-UG 23 -
ご清聴ありがとうございました 質問・フィードバックをお待ちしています RFC: aws-amplify/amplify-backend#3211 実装: aws-devtools-labs/aws-blocks の packages/hosting X (Twitter):
@fossamagna GitHub: @fossamagna AWS CDK Conference Japan 2026 presented by JAWS-UG 24