Next.js / Github-Actions로 AWS S3 + CloudFront에 배포하기

Hoon·2022년 12월 24일
4

Infra

목록 보기
1/10
post-thumbnail

기존 ec2 + pm2로 배포되있던Next.js 로 만든 프로젝트를 S3 + CloudFront로 변경하여 배포하는 작업을 맡았고 더불어 git-action를 통한 CI/CD 까지 구축하게되었다.

Next.js의 빌드는 React의 빌드와 조금 다른면이 있어 S3를 통한 정적 페이지 배포시에는 package.json 파일에 아래와 같이 추가해주었다. 또한 next.config.js Image loader 부분에 이슈가 있어 아래와 같이 추가해주었다.

// package.json
"scripts": {
	"deploy": "next build && next export"
}
// next.config.js
  trailingSlash: true,
  images: {
    loader: 'akamai',
    path: '/',
  },

next build의 경우 .next 라는 폴더에 애플리케이션에 빌드하지만 next export의 경우 .out 폴더에 모든페이지를 정적 HTML 파일로 내보낸다 !

또한 next export 시 예를들어 /page/user/index.tsx 파일의 경우 /user.html 로 빌드되는데 위의 next.config.jstrailingSlash: true 를 설정하게 되면 /user/index.html 로 빌드되는 것을 볼 수 있다. (해당 설정 이유는 아래에서 다룰 예정)

아래의 설명에 AWS IAM 설정은 제외한다. (S3, Cloudfront, Route53, ACM 관련 정책 추가)


1. S3 버킷 생성

위와 같이 버킷 이름 입력 후, AWS 리전을 선택해 주고 버킷을 생성한다.


2. S3 설정

S3 버킷 선택 -> 속성에서 정적 웹 사이트 호스팅 관련 index.html 설정을 해준다.

또한 권한에서 퍼블릭 엑세스 차단을 해제 후
아래와 같이 버킷 정책을 설정해주어 S3 엑세스퍼블릭으로 설정해준다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::{{S3 버킷 이름}}/*"
        }
    ]
}

3. CloudFront 생성

CloudFront 배포 생성을 클릭 후 원본 도메인 은 위에서 생성한 S3 버킷 을 선택해준뒤 아래와 같이 설정해준다.


또한 Route 53 도메인 설정을 위하여 사용자 정의 SSL 인증서 를 선택해야하는데 인증서 요청 을 클릭 하여 ACM 인증서 를 요청하여 선택해줘야하는데, 인증서 요청 후 CloudFront 를 생성한 후

Route 53 에서 원하는 도메인에 대한 레코드를 생성해준다. 이때, 별칭은 생성한 CloudFront를 선택해 준다. (S3, CloudFront, Route 53, ACM 인증서 이름은 모두 생성을 원하는 도메인명으로 !)
-> 생성 완료시 요청한 ACM 인증서가 적격, 사용중 으로 변경됨. (시간은 걸릴 수 있음!)


4. git-actions 설정

이제 어느정도 배포 설정은 마무리 되었으니 git-actions 를 통한 CI/CD를 구축하여 원하는 git branchpush 혹은 merge 시에 자동으로 빌드S3에 업데이트 하는 설정을 해볼려고한다.

먼저 .github/workflows 폴더를 생성 후 원하는 branch 이름 을 따서 main.yml 혹은 develop.yml 파일을 만들어 준다.

name: {{이름}}

on:
  push:
    branches: {{branch 이름}}
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Git Checkout
        uses: actions/checkout@v3

      - name: Use Node.js version 14.x
        uses: actions/setup-node@v3
        with:
          node-version: 14.x
          cache: yarn
          cache-dependency-path: yarn.lock

      - name: yarn deploy
        env:
          NEXT_PUBLIC_API_URL: {{환경 변수 설정 부분}}
        run: |
          npm install -g yarn
          yarn install --frozen-lockfile
          yarn deploy

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Deploy to S3
        env:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          aws s3 rm s3://{{S3 버킷 이름}} --recursive
          aws s3 cp --recursive --region ap-northeast-2 ./out s3://{{S3 버킷 이름}}

      - name: Clear Cache to Cloudfront
        env:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: aws cloudfront create-invalidation --distribution-id {{CloudFront ID}} --paths "/*"

secrets 관련 key의 경우 git 레포지토리의 setting -> Security -> Secrets -> Actions 에서 미리 설정할 수 있다.

위의 yml파일 맨 아래의 name : Clear Cache to Cloudfront 의 경우 CloudFront 는 기본 캐시 설정 시간이 24시간으로 설정되어있어 배포시 즉시 적용이 되지 않으므로 캐시를 무효화 해주는 부분의 작업이다.


5. 추가 설정

이제 S3, CloudFront 값 혹은 도메인 명 으로 접속시에 잘 배포된것을 확인할 수 있다.
하지만 하나의 문제점이 발견되었는데, 위에서 trailingSlash: true을 설정해준덕에 /page/user/index.tsx 파일이 /user/index.html 로 잘 빌드되어 배포환경에서 /user로 접속시 페이지가 잘 나타나는 것을 확인할 수 있다.
하지만 새로고침시/user/index.html 를 찾는것이 아닌 /user 를 찾게되어 403 오류 가 발생한다.

이에 먼저, 403 오류 시에 index.html리다이렉션 설정을 아래와 같이 해주었다.

또한, 새로 고침시에 /user/index.html 을 찾을 수 있도록 CloudFront 에서 함수를 하나 설정해 주었다.

// url-rewrite-single-page-apps

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 에서 동작 에서 앞에서 생성한 함수를 연결하여 동작 생성을 해주면 이슈없는 배포 끝 ! 👍👍👍

profile
4년차 개발자 Hoon입니다

0개의 댓글