VercelからS3にサイトを移行した

2024/03/23 荒井 雄治朗

Vercelで会社のサイトを動かしていたけど、動的な部分はないのでNext.jsを静的エクスポートしたものをS3に置いてCloudFront経由で配信する構成に変更した。
変更した時の作業メモ。

やりたいこと

Next.js の静的出力設定

next.config.js に以下の設定を追加する。

const nextConfig = {
    trailingSlash: true,
    output: 'export',
    distDir: 'dist',
    :
    :
};

trailingSlash: true は、URLの最後に / をつける設定で、これをつけることで aaaaa.html じゃなくて、 aaaaa/index.html という形で出力されるようになる。
こうすることでなるべくVercelで動かしていた時と同じURLで配信できるようになる。

output: 'export', は、静的出力するための設定。以前は next export コマンドを使って静的出力していたが、このコマンドは非推奨になっていて、output で設定するようになった。
この設定をすることで next build を実行した時に静的出力されるようになる。

distDir は出力先のディレクトリ設定。設定しなくても良い。

S3のバケットを作成する

静的出力したファイルを置くためのS3のバケットを作成する。大体の設定はデフォルトで、以下の設定を追加する。
CloudFrontからアクセスできるようにするためのバケットポリシーは、後から設定する。

CloudFront ディストリビューションを作成する

S3のバケットポリシーを設定する

S3のバケットポリシーを更新して、CloudFrontからS3のオブジェクトにアクセスできるようにする。
S3のバケット > アクセス許可 > バケットポリシー

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::www.anytools.app/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "[CloudFrontディストリビューションのARNを指定する]"
                }
            }
        }
    ]
}

ここまでで、CloudFront経由でS3に静的ファイルを配信する準備ができた。

ドメインの設定

www.anytools.app をCloudFront+S3に設定する。ACMで証明書を作成する必要があるが、ここで問題が発生した。既存のドメインがVercelを向いていると証明書の作成に失敗してしまう。
失敗する理由を調べてみると、VirusTotalで問題と判定されるドメインの場合に証明書の作成に失敗するようだった。
解決方法は、ドメインを一度Vercelから外してから証明書を作成すること以外に思いつかなかったので、ドメインの向き先をCloudFrontに向けた。結果、次の日の朝に証明書を作成したら成功した。

DNSの設定

CNAME: www.anytools.app -> CloudFrontのドメイン名

証明書の作成

ACMで証明書をリクエストして、DNSにACMが指定したCNAMEレコードを作成する。

CloudFrontの設定

一般 > 設定 > 代替ドメイン名 (CNAME) - オプション

に、証明書を作成したドメイン名を設定する。

カスタム SSL 証明書 - オプション に作成した証明書を設定する。

これで、ドメインの設定が完了した。

ディレクトリのURLを指定したら index.html を返すようにCloudFront Functions を設定する。

このサンプルコードを参考に(というかそのまま)、CloudFront Functions を設定する。

async function handler(event) {
    const request = event.request;
    const 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;
}

Github Actionsでビルドしてデプロイする

.github/workflows/deploy.yml を作る。

name: Deploy Coporate Site to S3
on:
  push:
      branches:
        - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
 
      - uses: pnpm/action-setup@v2.2.2
        with:
          version: 8.14.1
 
      - name: Package install
        run: pnpm i --frozen-lockfile
 
      - name: Build
        run: pnpm build
 
      - name: Deploy
        working-directory: dist
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: 'ap-northeast-1'
        run: |
          aws s3 sync . s3://[バケット名] --delete
 
      - name: make invalidation to cloudfront
        uses: chetan/invalidate-cloudfront-action@v2
        env:
          DISTRIBUTION: ${{ vars.DISTRIBUTION }}
          PATHS: '/*'
          AWS_REGION: 'ap-northeast-1'
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
 

デプロイ用のIAMユーザーを作成する

S3にデプロイするためのIAMユーザーを作成する。

Github Secrets に AWS のアクセスキーを設定する

AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY を設定する。

Variables にDISTRIBUTIONを指定する

DISTRIBUTION は CloudFrontのディストリビューションIDを設定する。

完成!