Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AmplifyHostingConstructからSSRフレームワークのためのホスティング設計...

 AmplifyHostingConstructからSSRフレームワークのためのホスティング設計を考察する/amplify-hosting-construct

Avatar for MURAKAMI Masahiko

MURAKAMI Masahiko

July 18, 2026

More Decks by MURAKAMI Masahiko

Other Decks in Technology

Transcript

  1. 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
  2. 自己紹介 名前: 村上 雅彦 所属: 株式会社永和システムマネジメント コミュニティ活動: 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
  3. このセッションで話すこと 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
  4. 題材は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
  5. 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
  6. 移管後は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
  7. 全体アーキテクチャ 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
  8. 設計判断① あなたならどうする? 「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
  9. 設計判断① 答えは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
  10. 設計判断① 「契約」にすると何が良いのか 「設定」: 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
  11. 設計判断② 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
  12. なぜ壊れるのか、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
  13. 設計判断② あなたならどうする? 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
  14. 設計判断② 実際は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
  15. 設計判断② 採用された構成 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
  16. 設計判断② 採用構成を実コードで見る 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
  17. 同じ問題への別解、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
  18. 気になったので、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
  19. 設計判断③ CloudFrontのルーティング、どう並べる? マニフェストにはリテラルもワイルドカードも混在したルートが並ぶ /api/edge/special ← リテラル /api/edge/* ← ワイルドカード /_next/static/*

    /* CloudFrontのビヘイビアは「先着一致」 。並べ方を間違えると /api/edge/special が /api/edge/* に食われる あなたなら、この並び順をどう決めますか? AWS CDK Conference Japan 2026 presented by JAWS-UG 19
  20. 設計判断③ スコアリングで機械的に順序を決める // 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
  21. 設計判断③ そして設計は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
  22. 今日、紹介しきれなかった工夫たち 工夫 内容 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