はじめに
今回はNextjsを静的出力し、S3に格納、CloudFront経由で公開までをやってみようと思います。
基本的にAWSの操作はマネジメントコンソール上で行い、CICDの設定や、CloudFormationなどは行いません。
Nextjsのプロジェクトを作成
サクッとNextjsのプロジェクトを作成します。
npx create-next-app@latest --typescript
What is your project named? ... static-app
Would you like to use ESLint with this project? ... Yes
Would you like to use `src/` directory with this project? ... Yes
Would you like to use experimental `app/` directory with this project? ... No
What import alias would you like configured? ... @/*
これでstatic-appが作成されました。
静的出力の設定
Nextjsをビルドした際に出力されるファイルを設定します。
CloudFrontの仕様上、このまま出力した場合、うまく行かないため、next.config.jsに下記オプションを追加します。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
trailingSlash: true,
}
module.exports = nextConfig
また、静的出力機能を利用するために、package.jsonも少し変更します。
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start",
"lint": "next lint"
},
これで、CloudFrontで利用できる形になりました。
ページ作成
今回は動くことを目標とするため、画面の作りこみは行いません。
デフォルトのフォルダやファイルに少し手を加えて、簡易的なページを作成し、正しく表示されるか確認したいと思います。
├─pages
│ │ index.tsx
│ │ _app.tsx
│ │ _document.tsx
│ │
│ └─about
│ index.tsx
│
└─styles
globals.css
Home.module.css
import Head from "next/head";
import { Inter } from "@next/font/google";
import styles from "@/styles/Home.module.css";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<p className={inter.className}>ルートページです。</p>
</main>
</>
);
}
import Head from "next/head";
import { Inter } from "@next/font/google";
import styles from "@/styles/Home.module.css";
const inter = Inter({ subsets: ["latin"] });
export default function About() {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<p className={inter.className}>アバウトページです。</p>
</main>
</>
);
}
動作確認
コマンドを入力して、ビルド&静的出力を行います。
npm run build
すると、static-app/outフォルダが生成されます。
このフォルダの中身をS3にデプロイし、CloudFrontを利用して公開していきます。
S3にデプロイ
まずはS3にデプロイするためにバケットを作成します。
なお、今回はCloudFrontを経由させるため、ブロックパブリックアクセスの設定は不要です。
CloudFrontを経由させず、直接バケットをWebサイトとして公開する場合、こちらの内容を設定する必要があります。

バケットが作成されたら、ここに先ほどNextjsで作成したWebサイトをデプロイします。
static-app/out内のものをすべてドラッグ&ドロップです。
CloudFrontの作成
CloudFrontを作成していきます。
オリジンドメインからS3のバケットを選択することができますので、
先ほど作ったS3バケットを選択します。

次にCloudFrontからS3バケットにアクセスできるようにするために、S3バケットアクセスの設定を行います。
Origin access control settings (recommended)にチェックを入れ、プルダウンより既存のOrigin access controlを選択します。
新規作成する場合は、コントロール設定を作成を押下します。
署名リクエスト (推奨)にチェックを入れ、作成を押下します。

S3 バケットポリシーを更新する必要があります
CloudFront は、ディストリビューションの作成後にポリシーステートメントを提供します。
と表示されていますが、一旦このままで大丈夫です。
この後、設定します。
また、残りの項目はデプロイを優先するためにデフォルトのまま進めていきます。
ディストリビューションを作成を押下することで、ディストリビューションが作成されます。
S3 バケットポリシーの更新
画面が遷移すると画面上部にS3 バケットポリシーについての案内が来るので、ポリシーをコピーからコピーしてS3 バケットポリシーを更新します。
あとからコピーしたい場合や、コピーし逃した場合であっても
ディストリビューションを選択して、オリジンタブからオリジンを編集を押下すると、同じものがコピーすることができます。
内容はCloudFrontのディストリビューションからS3バケットに対してのアクセス許可なので、自分で書いてしまっても問題ないです。
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::next-static-web-site/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::自分のID:distribution/EGDE7KD6UY0RT"
}
}
}
]
}
これをS3のバケットポリシーにあてていきます。
Amazon S3のバケットからアクセス許可のバケットポリシーを編集から、コピーしたものを入力します。
これで、CloudFrontからS3 バケットにアクセスできるようになります。
実際にアクセスしてみる
CloudFrontよりディストリビューションを選び、ディストリビューションドメイン名に表示されているURLにアクセスします。
なお、ルートにアクセスしてもindex.htmlを返却するようにしていないため、正しく表示されません。
一旦/index.htmlを付けてアクセスします。
続いて/about/index.htmlを付けてアクセスします。

問題なく公開されていそうです。
CloudFrontの関数を定義する
アクセスした際に、わざわざindex.htmlをつけるのは面倒なので、
自動でindex.htmlが付与されるようにCloudFrontに関数を定義します。
CloudFront内の左のメニューより関数を選択し、関数を作成を押下します。

名前を付け、関数を新規作成すると、関数コードがあるのでそこに下記Javasctiptを記載し、変更を保存を押下します。

function handler(event) {
var request = event.request;
var uri = request.uri;
// Check whether the URI is missing a file name.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Check whether the URI is missing a file extension.
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
ディストリビューション一覧画面に戻って対象のディストリビューションを選択し、
ビヘイビアタブからデフォルトのものを選択し、編集を押下します。
関数の関連付けで、ビューワーリクエストから関数タイプCloudFront Functionsを選択し、先ほど作った関数を選択、変更を保存します。

これでindex.htmlなしでもindex.htmlが表示されるようになりました。
まとめ
CloudFrontとS3を利用することで、キャッシュを効かせながらサーバレスで静的サイトを簡単に公開できました。
次はGithubからS3バケットに自動デプロイできるようなCICDや、S3とCloudFrontの設定をCloudFormationで構築してみたいと思いました。
参考







