LoginSignup
20
10

More than 1 year has passed since last update.

Nextjsで作成したWebサイトをCloudFront+S3で公開する

Posted at

はじめに

今回は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に下記オプションを追加します。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  trailingSlash: true,
}

module.exports = nextConfig

また、静的出力機能を利用するために、package.jsonも少し変更します。

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
index.tsx
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>
    </>
  );
}

about.tsx
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にデプロイするためにバケットを作成します。

バケットを作成を押下し、必要な項目を適当に埋めていきます。
image.png

なお、今回はCloudFrontを経由させるため、ブロックパブリックアクセスの設定は不要です。
CloudFrontを経由させず、直接バケットをWebサイトとして公開する場合、こちらの内容を設定する必要があります。
image.png

バケットが作成されたら、ここに先ほどNextjsで作成したWebサイトをデプロイします。
static-app/out内のものをすべてドラッグ&ドロップです。

image.png

CloudFrontの作成

CloudFrontを作成していきます。

オリジンドメインからS3のバケットを選択することができますので、
先ほど作ったS3バケットを選択します。
image.png

次にCloudFrontからS3バケットにアクセスできるようにするために、S3バケットアクセスの設定を行います。

Origin access control settings (recommended)にチェックを入れ、プルダウンより既存のOrigin access controlを選択します。
新規作成する場合は、コントロール設定を作成を押下します。

署名リクエスト (推奨)にチェックを入れ、作成を押下します。
image.png

S3 バケットポリシーを更新する必要があります
CloudFront は、ディストリビューションの作成後にポリシーステートメントを提供します。

と表示されていますが、一旦このままで大丈夫です。
この後、設定します。
また、残りの項目はデプロイを優先するためにデフォルトのまま進めていきます。
ディストリビューションを作成を押下することで、ディストリビューションが作成されます。

S3 バケットポリシーの更新

画面が遷移すると画面上部にS3 バケットポリシーについての案内が来るので、ポリシーをコピーからコピーしてS3 バケットポリシーを更新します。

image.png

あとからコピーしたい場合や、コピーし逃した場合であっても
ディストリビューションを選択して、オリジンタブからオリジンを編集を押下すると、同じものがコピーすることができます。

image.png

内容は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を付けてアクセスします。

スクリーンショット 2023-03-02 134741.png

表示されました。
image.png

続いて/about/index.htmlを付けてアクセスします。
image.png

問題なく公開されていそうです。

CloudFrontの関数を定義する

アクセスした際に、わざわざindex.htmlをつけるのは面倒なので、
自動でindex.htmlが付与されるようにCloudFrontに関数を定義します。

CloudFront内の左のメニューより関数を選択し、関数を作成を押下します。
image.png

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

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;
}

発行タブから関数を発行を押下して、関数を発行します。
image.png

ディストリビューション一覧画面に戻って対象のディストリビューションを選択し、
ビヘイビアタブからデフォルトのものを選択し、編集を押下します。

image.png

関数の関連付けで、ビューワーリクエストから関数タイプCloudFront Functionsを選択し、先ほど作った関数を選択、変更を保存します。
image.png

これでindex.htmlなしでもindex.htmlが表示されるようになりました。

まとめ

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

参考

20
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
10