はじめに

こんにちは!
CI事業部ソリューション開発セクションの古賀です。

CloudFront Hosting Toolkitは以下の構成の
フロントエンドのホスティングとCI/CDパイプラインを簡単に構築できる強力なツールです。

標準でサポートされているフレームワークには、ReactやVue.jsなどがありますが
これら以外のフレームワークを使用したい場合もあるでしょう。
今回はCloudFront Hosting Toolkitの基本的な使用方法と
標準でサポートされていないフレームワークを使用する方法を紹介します。

事前準備

以下のツールをインストールして、設定しておく必要があります。

  • Node.js 18+
  • AWS CLI v2(プロファイルを設定しておく)

※CloudFront Hosting Toolkitにはプロファイルを渡せない

  • CloudFront Hosting Toolkit
npm install -g @aws/cloudfront-hosting-toolkit

※グローバルインストールする

  • アプリのソースをgithubにあげておく。

基本的な流れ

設定ファイルの準備

cloudfront-hosting-toolkit init

使用するgithubリポジトリ、ブランチ、フレームワーク
ドメイン(オプション)を設定します。

.
└── cloudfront-hosting-toolkit
├── cloudfront-hosting-toolkit-build.yml
├── cloudfront-hosting-toolkit-cff.js
└── cloudfront-hosting-toolkit-config.json

cloudfront-hosting-toolkitフォルダと、その配下に設定ファイルが作成されます。

ファイル名 内容
cloudfront-hosting-toolkit-build.yml CodeBuildのbuildspec.yml
cloudfront-hosting-toolkit-cff.js CloudFront Functionsの関数コード
cloudfront-hosting-toolkit-config.json `cloudfront-hosting-toolkit init`で設定した内容

デプロイ

cloudfront-hosting-toolkit deploy

1.に記載のURLを開き、保留中となっている接続を設定します。
設定したらターミナルでokを入力して待ちます。
数分後にデプロイが完了します。

削除

デプロイで作成したリソースを削除します

cloudfront-hosting-toolkit delete

標準でサポートしていないフレームワークを使用する

標準では以下のフレームワークとフレームワークなしをサポートしています。

React.js
Next.jx
Angular.js
Vue.js
Astro

Next.jsはサポートしているのに、Nuxt.jsはサポートしていないので
Nuxt.jsを使ってデプロイしてみます。

Nuxt.jsのソースを準備する

以下のコマンドでNuxt.jsのプロジェクトを作成します。

npx nuxi@latest init nuxt-app

SSGを行うために、nuxt.config.tsにssr: false,を追記します。

Cloudfront Hosting Toolkitを設定する

以下のコマンドでグローバルインストールしたnpmパッケージが配置される
ディレクトリを確認します。

npm root -g

出力例

/home/vscode/.nvm/versions/node/v20.17.0/lib/node_modules

この出力例の場合だとcloudfront-hosting-toolkitは

/home/vscode/.nvm/versions/node/v20.17.0/lib/node_modules/@aws

にあります。

cloudfront-hosting-toolkit/resources
├── build_config_templates
│ ├── hosting_angularjs.yml
│ ├── hosting_basic.yml
│ ├── hosting_nextjs.yml
│ ├── hosting_reactjs.yml
│ ├── hosting_vuejs.yml
│ └── s3_build_config.yml
├── cff_templates
│ ├── index_angularjs.js
│ ├── index_basic.js
│ ├── index_nextjs.js
│ ├── index_reactjs.js
│ └── index_vuejs.js
...

resources/build_config_templates配下にhosting_[フレームワーク名].ymlを作成します。
今回はhosting_nuxtjs.ymlを作成します。

hosting_Nuxtjs.yml

version: 0.2

phases:
build:
commands:
- npx npm install
- npx npm run generate
- cd dist
- echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line
- aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line

resources/cff_templates配下にindex_[フレームワーク名].jsを作成します。
今回はindex_nuxtjs.jsを作成します。

index_Nuxtjs.js

import cf from 'cloudfront';

const kvsId = '__KVS_ID__';

// This fails if the key value store is not associated with the function
const kvsHandle = cf.kvs(kvsId);

function pointsToFile(uri) {
return /\/[^/]+\.[^/]+$/.test(uri);
}
var rulePatterns = {
"/$": "/index.html", // When URI ends with a '/', append 'index.html'
"!file": ".html", // When URI doesn't point to a specific file and doesn't have a trailing slash, append '.html'
"!file/": "/index.html",// When URI has a trailing slash and doesn't point to a specific file, append 'index.html'
};

// Function to determine rule and update the URI
async function updateURI(uri) {

let pathToAdd = "";

try {
pathToAdd = await kvsHandle.get("path");
} catch (err) {
console.log(`No key 'path' present : ${err}`);
return uri;
}

// Check for trailing slash and apply rule.
if (uri.endsWith("/") && rulePatterns["/$"]) {
return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["/$"];
}

// Check if URI doesn't point to a specific file.
if (!pointsToFile(uri)) {
// If URI doesn't have a trailing slash, apply rule.
if (!uri.endsWith("/") && rulePatterns["!file"]) {
return "/" + pathToAdd + uri + rulePatterns["!file"];
}

// If URI has a trailing slash, apply rule.
if (uri.endsWith("/") && rulePatterns["!file/"]) {
return "/" + pathToAdd + uri.slice(0, -1) + rulePatterns["!file/"];
}
}
return "/" + pathToAdd + uri;
}

// Main CloudFront handler
async function handler(event) {
var request = event.request;
var uri = request.uri;

//console.log("URI BEFORE: " + request.uri); // Uncomment if needed
request.uri = await updateURI(uri);
//console.log("URI AFTER: " + request.uri); // Uncomment if needed

return request;
}

2つのファイル作成後に
cloudfront-hosting-toolkit initを行うとフレームワークを選択する部分で
nuxtjsが表示されるようになります。

今回追加したNuxt.jsだけnuxtjsと表示される原因として
標準でサポートしているフレームワークは
@aws/cloudfront-hosting-toolkit/bin/cli/shared/constants.js内で
以下の定数が定義されていました。

exports.FRAMEWORKS = {
reactjs: "React Framework",
nextjs: "Next.js Framework",
angularjs: "AngularJS Framework",
vuejs: "Vue.js Framework",
astro: "Astro Framework",
basic: "No FrontEnd framework used; Basic implementation (no build required)",
};

Nuxt.jsも追記すると、他のフレームワークと同様に表示されます。

exports.FRAMEWORKS = {
reactjs: "React Framework",
nextjs: "Next.js Framework",
angularjs: "AngularJS Framework",
vuejs: "Vue.js Framework",
nuxtjs: "Nuxt.js Framework",
astro: "Astro Framework",
basic: "No FrontEnd framework used; Basic implementation (no build required)",
};

以降は基本的な流れと同じ手順でデプロイができます。

おまけ

v13.3以降のNext.jsをデプロイする際の注意点

cloudfront-hosting-toolkit v1.1.7では
hosting_nextjs.ymlは以下の内容となっています。

hosting_nextjs.yml

version: 0.2

phases:
build:
commands:
- n install 18.17.0
- npx npm install
- npx next build
- npx next export
- cd out
- echo aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line
- aws s3 cp ./ s3://$DEST_BUCKET_NAME/$CODEBUILD_RESOLVED_SOURCE_VERSION/ --recursive #don't change this line

Next.jsのv13.3以降ではStatic exportの方法が
npx next exportを実行する方法から
next.config.jsでoutput: 'export'を指定する方法に変更となっています。

そのため、Next.jsのv13.3以降を使用する場合では以下の2点の対応が必要です。

  1. next.config.jsにoutput: 'export'を記載します。
  2. build_config_templates/hosting_nextjs.ymlからnpx next exportを削除して
    cloudfront-hosting-toolkit initを実行する
    または、cloudfront-hosting-toolkit init実行後にcloudfront-hosting-toolkit-build.ymlから- npx next exportを削除する