프로젝트를 진행하면서 배포를 간단히 할 수 있는 방법으로 next-serverless
를 사용하였다. 근데 그땐 그냥 다른 래퍼런스 참고해서 내 환경에 맞게 작업하여 완성하였다. 근데 어떤 동아리 면접에서 serverless 배포를 어떻게 했는 지 물어봤을 때 내가 정확하게 알고 있는 지 확신이 없었다. 그래서 next-serverless에서는 어떻게 코드가 배포되고 이를 실행할 수 있는 지 자세히 알아보자.
[요약]
1. /_next, /public 에는 정적자원을 저장하고 /static-pages 에는 SSR 렌더링하는 페이지와 앞에서 매칭되지 않은 static 페이지를 가져온다. /static-pages 파일들은 lambda function 이 연결되어 데이터를 가져온다. (디렉토리는 S3(origin)에 존재)
2. 사용자 요청이 들어오면 cloudfront는 가장 가까운 엣지 로케이션에서 캐싱된 데이터를 찾고, 없으면 origin(s3)에서 불러온다.
3. 불러온 데이터를 사용자에게 반환하고 일정기간 캐싱한다.
전 세계 곳곳에 퍼져있는 엣지 로케이션에 데이터를 캐싱하여 정적 및 동적 웹 콘텐츠를 사용자에게 더 빨리 배포하도록 지원하는 서비스
사용자는 지리적으로 가까운 곳의 캐시서버에서 데이터를 받아온다. 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으로 뜨는 걸 확인할 수 있다.
AWS CloudFront 내부에서 4가지 캐시 동작 존재
- build assets
(_next/*)
- user assets
(static/*)
- SSR and static pages
- API routes
(api/*)
첫 번째와 두 번째 캐시 동작인 _next/*
및 static/*
은 요청을 Amazon S3로 전달. 정적 자원을 제공.
세 번째 캐시 동작은 lambda function
과 연결되어 있으며 세 가지 타입의 request 존재
- 서버 측 렌더링 페이지:
getInitialProps
메서드를 정의하는 페이지의 요청을 처리하고 응답을 즉시 사용자에게 반환. 동적으로 렌더링되는 페이지.- 정적으로 최적화된 페이지: Next.js에 의해 사전 컴파일된 페이지의 HTML 요청을 Amazon S3로 전달. 사전 컴파일된 정적 페이지.
- 공용 리소스:
/robots.txt
,/favicon.ico
,/manifest.json
등과 같은 루트 레벨 리소스의 요청을 처리하고 이러한 요청을 Amazon S3로 전달.
2번, 3번 request가 정적 자원인데 Lambda@Edge를 통해 처리되는 이유는 요청 경로가 _next/*
또는 static/*
과 같은 패턴을 따르지 않기 때문.
또한, CloudFront 배포당 캐시 동작 수가 최대 25개로 제한되기 때문에 모든 경로에 대해 별도의 캐시 동작을 만드는 것은 좋지 않음.
네 번째 캐시 동작은 api/*
경로로 들어오는 Next API 요청 처리
CloudFront의 distribution id를 도메인으로 쓰긴 안이쁘니까 보통 custom domain을 지정한다.
도메인명은 아무거나 쓸 수 있지만 AWS Route53을 사용하여 DNS 호스팅을 해야한다. 따라서 다음 2가지 조건을 만족해야 한다.
- Route 53에 도메인에 대한 호스팅 영역(hosted zone) 포함
- 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 ]
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-정적-컨텐츠-제공하기