트러블이슈

Next.js를 s3+clouldFront에 배포하기

Ahyeon, Jung 2024. 6. 24. 01:55

??: 배포 해주세요.

나: vercel...

??: 저희 도메인 쓰십시다.

나: vercel.

??: 이미지 추가하고 싶어요

나: vercel + s3


나: 백엔드분들이 cs 뭔가 잘알던데, 백엔드를 내가 해보겠따..!

??: 클라우드 때문임. 클라우드 공부ㄱㄱ


사실 지금까지 인프라에 대해서 크게 관심이 간 건 아니었다. VM아니면 vercel로 대충 배포하면 알아서 해주기 때문에, 나는 애플리케이션에만 집중하면 될거라고 생각했다. 하지만 HTTP 완벽 가이드를 읽으면서 계속이해된다기 보다는 그냥 텍스트를 읽는 경향이 강해졌다. 백엔드를 직접 만들어보면 되지 않을까 싶어서 그냥 기본적인 서비스와 컨트롤러로 CRUD만 구성해보기도 했지만, 백엔드를 만들 줄 안다고 해서 통신 과정을 이해할 수 있는 건 아니었다. 아직 깊이가 없어서 그런 가 싶어서 일단 하던 중에 테크톡에서 vercel은 신이 맞고, 클라우드를 배워야 좀 감이 잡힐거라는 얘기를 들었다. 코치님도 세번이나 말하기도 했고,, 그리고 다음날, github organization을 vercel로 가져와보니 유료계정이 필요했다. 그냥 내 계정으로 fork해와서 배포할 수도 있지만,, HTTP 완벽 가이드를 보고 처참한 내 네트워크 실력을 확인했기에, 인프라를 시도해보았다. 사실 그 사이에 이미 netlify로 배포를 해놔서 상관없었는데, 이왕 클라우드 공부하기로 결심한거 일단 체험해보고 싶었다.


s3

정적 파일을 저장하고 제공하는 역할

clouldFront

정적 파일을 저장하고 제공하는 역할

해당 파일을 전 세계에 분산된 엣지 로케이션에서 빠르게 서비스하는 CDN 역할

SSR을 위해서는 s3 + cloudFront하면 안된다

사실 일단 프론트는 s3 + cloudFront로 배포한다길래 일단 시도해본거였다. 하지만 마지막에 Next.js를 정적 파일로 생성해놓고 나서야 깨달았다. 근데 뭐, 사실 서버에서 accessToken을 header로 줘버려서 이미 서버 컴포넌트와 클라이언트 컴포넌트의 경계가 사라진 즈음이라, 그냥 진행했다. 서버사이드렌더링을 위해서는 서버를 띄워야하므로, AWS의 Elastic Beanstalk, EC2 등을 사용한다고 한다. Serverless 아키텍처로 AWS Lambda를 사용할 수도 있다.


1. s3 버킷 생성하기

 

2. clouldFront 생성하기

 

3. AWS 설치 및 계정 설정하기

1. AWS 설치

명령어를 입력할 수 있게 AWS CLI를 설치한다.

 

2. AWS 계청 입력

AWS 계정을 입력해 권한을 부여한다.

aws configure

 

4. 빌드 파일 업로드

next build를 실행하여 정적 파일을 생성하고, 해당 파일을 s3 버킷에 복사한다.

aws s3 cp --recursive --region ap-northeast-2 ./.next s3://sinchulgwinong

 

실제로 버킷을 열어보면 파일이 잘 들어간 것을 확인할 수 있다.

5. 도메인에 접속하여 확인하기

clouldFront를 생성할 때 입력했던 도메인으로 접속하면 정적 파일을 전달받을 수 있다.

하지만 나는 도메인명 어떤거 했는지 까먹었다.

 aws cloudfront list-distributions

  

위의 명령어를 입력하면 연결된 도메인을 알려준다.

 

드디어 접속하면..!

404가 뜬다..

사실 버킷에 들어간 파일들 보면서 예상하긴 했지만,, 들어간 파일에 index.html이 없었다.

아마도 react는 /dist에 바로 정적파일을 빌드해주는데, Next는 SSR로 불러오기 때문으로 보인다.


Next에서 정적 파일 export 하기

일단 검색해보면, next export를 하면 /dist와 같이 /out에 정적 파일들을 생성해준다고 한다.

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "deploy": "next build && next export",
  ...
},

 

 

하지만 막상 deploy를 해보면 에러가 뜨면서 output: "export"를 추가해달라고 한다.

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "export",
  trailingSlash: true,
};

export default nextConfig;

 

 

 

갑자기 output: export 추가하니, gerateStaticParams()가 없다고 한다. 

app router 사용하면서 Server API 다 사라졌다며,,? 하고 검색을 해보니,

애초에 다이나믹 라우팅을 빌드시점에 다 생성할 수 없어서 생기는 문제였다.

정확히 확인은 안해봤지만 pages router에서도 같은 문제가 발생할 것이라 생각한다.

 

이제 두가지 선택지가 있다.

 

1. 생성될 페이지를 모두 빌드시점에 API를 불러서 각 페이지를 생성한다.

2. 다이나믹 라우팅을 하지않고, URI Search를 활용하여 id를 가져온다.

 

Next의 action을 활용해서 DB의 boardId값을 전부 가져오는 방법도 생각해보았으나, 일단 시간이 없고, 사실상 게시글이 몇 천개 이상으로 나올 수 있는 상황에서 정적 페이지를 생성하는것은 불가능하고 빌드용량이 너무 커지기 때문에 2번 방식을 택했다.

 

문제가 있다면,, 당시 나는 header로 받아온 accessToken을 localStorage에 넣으면 서버 사이드 컴포넌트에서는 접근이 불가능하더라도, 클라이언트 사이드 컴포넌트에서는 접근할 수 있기 때문에 가능할 줄 알고 계속 진행하고 있었다는 사실이었다. 서버 콘솔 확인도 안하고 빌드 확인도 안한 내 잘못이긴 했지만, 어쨌든 이 때문에 main에 올리는게 불가능했다. 그래서 계속 빌드 해결하고 배포로도 해결해서 +1,642로 최종 PR올렸다.. 어차피 나혼자 하는거라 상관없긴 하지만 앞으로는 PR을 작게 올리자,,


다이나믹 라우팅 제거하기

/app/job/detail/[id]/page.tsx 에서 /app/job/detail로 변경하고,

기존에 useParams로 가져오던 boardId를 useSearchParams로 변경했다.

 

하지만 빌드해보니 여전히 에러가 있었다.

 

이게 왜 나타나는건지 처음에는 이해가 안갔는데, suspense boundary를 써서 크게 감싸도 에러가 생겨서 고민이 있었는데, 이게 Suspense로 걸린다고 해도 렌더링 이전에는 함수가 실행되는 거라고 생각해서, 그 위에서 잡았다. 이전에는 보통 전역으로 잡아서 아예 마운트를 방지하기 위해서는 Suspense를 부모 컴포넌트에서 잡아야했다.

 

빌드 성공을 만났을 때 진짜 행복하다.

파일을 보면 .next 외에 out에 정적파일이 모두 생성된 것을 확인할 수 있다.

 

이제 이 /out를 버킷에 올려주면 된다.

aws s3 cp --recursive --region ap-northeast-2 ./out s3://sinchulgwinong

 

이미지가 정적파일에 포함이 안된듯하고, CI/CD도 안해놨고, 서버에서 본 IP를 허용하지 않았지만,, 일단 성공~

신출귀농 (sinchulgwinong.s3-website.eu-north-1.amazonaws.com)

 

신출귀농

주변 농촌 일자리 를 소개할게요! 농촌 알바하고 돈도 벌고 힐링도 해보아요.

sinchulgwinong.s3-website.eu-north-1.amazonaws.com


CI/CD는 생각보다 간단하다. 그냥 AWS 인증과 배포를 github actions에게 위임하기만 하면 된다!

name: Node.js CI

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install Dependencies
        run: npm install --frozen-lockfile
      - run: npm ci

      - name: Set Environment Variables
        run: |
          echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env
          echo "NEXT_PUBLIC_KAKAO_MAP_API_KEY=${{ secrets.NEXT_PUBLIC_KAKAO_MAP_API_KEY }}" >> .env

      - run: npm run build --if-present
      - name: build
        run: npm run build

      - 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: ap-northeast-2

      - name: Upload to S3
        run: aws s3 cp --recursive --region ap-northeast-2 ./out s3://sinchulgwinong