[Next] next-serverless 내부 구조 이해하기

Gyuhan Park·2023년 11월 3일
2

nextjs

목록 보기
1/8

💭 TMI

프로젝트를 진행하면서 배포를 간단히 할 수 있는 방법으로 next-serverless 를 사용하였다. 근데 그땐 그냥 다른 래퍼런스 참고해서 내 환경에 맞게 작업하여 완성하였다. 근데 어떤 동아리 면접에서 serverless 배포를 어떻게 했는 지 물어봤을 때 내가 정확하게 알고 있는 지 확신이 없었다. 그래서 next-serverless에서는 어떻게 코드가 배포되고 이를 실행할 수 있는 지 자세히 알아보자.

[요약]
1. /_next, /public 에는 정적자원을 저장하고 /static-pages 에는 SSR 렌더링하는 페이지와 앞에서 매칭되지 않은 static 페이지를 가져온다. /static-pages 파일들은 lambda function 이 연결되어 데이터를 가져온다. (디렉토리는 S3(origin)에 존재)
2. 사용자 요청이 들어오면 cloudfront는 가장 가까운 엣지 로케이션에서 캐싱된 데이터를 찾고, 없으면 origin(s3)에서 불러온다.
3. 불러온 데이터를 사용자에게 반환하고 일정기간 캐싱한다.

☁️ CloudFront 란

전 세계 곳곳에 퍼져있는 엣지 로케이션에 데이터를 캐싱하여 정적 및 동적 웹 콘텐츠를 사용자에게 더 빨리 배포하도록 지원하는 서비스

사용자는 지리적으로 가까운 곳의 캐시서버에서 데이터를 받아온다. CDN은 정적 데이터 캐싱만 가능하지만 cloudfront는 정적, 동적 캐싱 모두 가능하다.

CloudFront는 엣지 로케이션과 Origin간의 동작으로 구성
엣지 로케이션(edge location) : 데이터가 캐싱되는 곳
origin : 데이터 원본이 위치한 곳 (S3, EC2, ELB 등)
CloudFront를 사용하여 S3에 저장된 정적 데이터를 제공할 수 있다.

사용자 요청이 들어오면 edge location에서 캐싱된 데이터를 찾아서 반환하는데, 데이터가 없다면 origin서버에서 찾아 보낸다.
이때 edge location은 해당 파일을 캐싱한다 (캐싱되는 시간 지정 가능)

cloudfront와 s3를 연동했다면 캐싱 데이터를 가져오는 지 확인할 수 있다.
네트워크 탭을 열고 [cloudfront 배포도메인]/[s3bucket 경로] 요청을 보내면 X-cache: hit from cloudfront 라는 헤더를 볼 수 있다. 첫 요청 시 miss 였다가 다음 요청은 edge location에 캐싱이 되서 hit으로 뜨는 걸 확인할 수 있다.

🏛️ architecture

AWS CloudFront 내부에서 4가지 캐시 동작 존재

  1. build assets (_next/*)
  2. user assets (static/*)
  3. SSR and static pages
  4. API routes (api/*)

첫 번째와 두 번째 캐시 동작인 _next/*static/*은 요청을 Amazon S3로 전달. 정적 자원을 제공.

세 번째 캐시 동작은 lambda function 과 연결되어 있으며 세 가지 타입의 request 존재

  1. 서버 측 렌더링 페이지: getInitialProps 메서드를 정의하는 페이지의 요청을 처리하고 응답을 즉시 사용자에게 반환. 동적으로 렌더링되는 페이지.
  2. 정적으로 최적화된 페이지: Next.js에 의해 사전 컴파일된 페이지의 HTML 요청을 Amazon S3로 전달. 사전 컴파일된 정적 페이지.
  3. 공용 리소스: /robots.txt, /favicon.ico, /manifest.json 등과 같은 루트 레벨 리소스의 요청을 처리하고 이러한 요청을 Amazon S3로 전달.

2번, 3번 request가 정적 자원인데 Lambda@Edge를 통해 처리되는 이유는 요청 경로가 _next/* 또는 static/* 과 같은 패턴을 따르지 않기 때문.
또한, CloudFront 배포당 캐시 동작 수가 최대 25개로 제한되기 때문에 모든 경로에 대해 별도의 캐시 동작을 만드는 것은 좋지 않음.
네 번째 캐시 동작은 api/* 경로로 들어오는 Next API 요청 처리

🏠 custom domain

CloudFront의 distribution id를 도메인으로 쓰긴 안이쁘니까 보통 custom domain을 지정한다.
도메인명은 아무거나 쓸 수 있지만 AWS Route53을 사용하여 DNS 호스팅을 해야한다. 따라서 다음 2가지 조건을 만족해야 한다.

  1. Route 53에 도메인에 대한 호스팅 영역(hosted zone) 포함
  2. domain registrar(예: Namecheap, GoDaddy 등)에 나열된 네임서버를 새로운 호스팅 영역에 제공된 네임서버로 갱신
# serverless.yml

myNextApplication:
  component: "@sls-next/serverless-component@{version_here}"
  inputs:
    domain: "example.com" # sub-domain defaults to www
    domainMinimumProtocolVersion: "TLSv1.2_2021" # can be omitted, defaults to "TLSv1.2_2018"

subdomain을 설정할 수도 있다.

# serverless.yml

myNextApplication:
  component: "@sls-next/serverless-component@{version_here}"
  inputs:
    domain: ["sub", "example.com"] # [ sub-domain, domain ]

☁️ cloudfront 환경설정

serverless.yml 파일을 이용해 원하는 옵션을 추가하여 cloudfront를 설정할 수 있다.
이미 생성한 cloudfront를 사용한다면 inputs: cloudfront: 를 설정해줘야 한다. cache TTL이나 certificate, origin 설정 등 기본값을 수정하고 싶은 부분만 건들이면 된다.

api : next.js api pages 캐싱
/[페이지명] : SSR을 다루는 페이지마다 다르게 캐싱전략 설정 가능
origins : custom origin 설정

# serverless.yml

myNextApplication:
  component: "@sls-next/serverless-component@{version_here}"
  inputs:
    cloudfront:
      # if you want to use an existing cloudfront distribution, provide it here
      distributionId: XYZEXAMPLE #optional
      # this is the default cache behaviour of the cloudfront distribution
      # the origin-request edge lambda associated to this cache behaviour does the pages server side rendering
      defaults:
        forward:
          headers:
            [
              CloudFront-Is-Desktop-Viewer,
              CloudFront-Is-Mobile-Viewer,
              CloudFront-Is-Tablet-Viewer
            ]
      # this is the cache behaviour for next.js api pages
      api:
        minTTL: 10
        maxTTL: 10
        defaultTTL: 10
      # you can set other cache behaviours like "defaults" above that can handle server side rendering
      # but more specific for a subset of your next.js pages
      /blog/*:
        minTTL: 1000
        maxTTL: 1000
        defaultTTL: 1000
        forward:
          cookies: "all"
          queryString: false
      /about:
        minTTL: 3000
        maxTTL: 3000
        defaultTTL: 3000
      # you can add custom origins to the cloudfront distribution
      origins:
        - url: /static
          pathPatterns:
            /wp-content/*:
              minTTL: 10
              maxTTL: 10
              defaultTTL: 10
        - url: https://old-static.com
          pathPatterns:
            /old-static/*:
              minTTL: 10
              maxTTL: 10
              defaultTTL: 10
        - url: http://old-api.com
          protocolPolicy: http-only
          pathPatterns:
            /old-api/*:
              minTTL: 10
              maxTTL: 10
              defaultTTL: 10
      aliases: ["foo.example.com", "bar.example.com"]
      priceClass: "PriceClass_100"
      # You can add custom error responses
      errorPages:
        - code: 503
          path: "/503.html"
          minTTL: 5 # optional, minimum ttl the error is cached (default 10)
          responseCode: 500 # optional, alters the response code
      comment: "a comment" # optional, describes your distribution
      webACLId: "arn:aws:wafv2:us-east-1:123456789012:global/webacl/ExampleWebACL/473e64fd-f30b-4765-81a0-62ad96dd167a" # ARN of WAF
      restrictions:
        geoRestriction:
          restrictionType: "blacklist" # valid values are whitelist/blacklist/none. Set to "none" and omit items to disable restrictions
          items: ["AA"] # ISO 3166 alpha-2 country codes
      certificate:
        cloudFrontDefaultCertificate: false # specify false and one of IAM/ACM certificates, or specify true and omit IAM/ACM inputs for default certificate
        acmCertificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
        iamCertificateId: "iam-certificate-id" # specify either ACM or IAM certificate, not both
        sslSupportMethod: "sni-only" # can be omitted, defaults to "sni-only"
        minimumProtocolVersion: "TLSv1.2_2019" # can be omitted, defaults to "TLSv1.2_2019"
      originAccessIdentityId: XYZEXAMPLE #optional
      paths: ["/*"] # which paths should be invalidated on deploy, default matches everything, empty array skips invalidation completely
      waitBeforeInvalidate: true # by default true, it waits for the CloudFront distribution to have completed before invalidating, to avoid possibly caching old page
      tags: # Add any tags you want
        tag1: val1
        tag2: val2

https://github.com/serverless-nextjs/serverless-next.js
https://three-beans.tistory.com/entry/AWSCloudFront-CloudFront로-S3-정적-컨텐츠-제공하기

profile
단단한 프론트엔드 개발자가 되고 싶은

0개의 댓글