이전 포스트 - Next.js로 정적 웹 사이트 개발하고 빌드하기에서 Next.js 를 통해 정적 웹 사이트를 개발하고 빌드하는 방법에 대해 소개하였다.
이후, 빌드를 수행하여 만들어 낸 번들을 AWS의 클라우드 서비스를 이용해 실제 사용자에게 제공하려면 어떻게 해야 하는지에 대한 내용을 담아볼 예정이다. 이를 위해 AWS의 S3, CloudFront, Route53 서비스를 활용할 것이다.
S3에서 정적 웹 사이트를 서비스 하기 위한 몇 가지 방법이 존재하는데, 세부 내용은 AWS re:Post 지식 센터 - CloudFront를 사용하여 Amazon S3에 호스팅되는 정적 웹 사이트를 서비스하려면 어떻게 해야 합니까?에 안내되어 있다.
배포를 진행하면서 각 방식마다 상이한 세부 설정 방법으로 인해 원활하게 진행되지 않았던 부분이 있어, 해당 내용에 관한 내용을 정리해보고 공유하기 위한 목적으로 글을 작성하였다.
참고용으로 간략한 구성도를 그려 보았다. 간단하게 설명을 덧붙이자면 다음과 같다.
https://example.com
접속을 요청한다.S3 REST API Endpoint
로 구성된 경우, 별도 CloudFront Function 구성 필요 XS3 Website Endpoint
로 구성된 경우, CloudFront Function 추가 구성 필요(파일명이 포함되지 않은 요청에 index.html
추가하는 함수)참고로 S3 버킷에는 퍼블릭 액세스를 차단할 수 있는 권한 옵션이 존재한다.
만약 버킷의 퍼블릭 액세스가 허용된 상태에서 S3 버킷에 배포하고자 하는 사이트 번들을 업로드한다면, 모든 사용자가 S3의 엔드포인트를 통해 직접 접근이 가능해지게 된다.
우리는 사용자들에게 엣지 로케이션의 이점을 활용하여 더 빠른 속도로 사이트를 제공하고자 하므로, 사용자가 S3가 아닌 CloudFront 배포를 통해서만 접근할 수 있도록 해야 한다.
따라서, 사용자가 S3 버킷의 엔드포인트에 직접 접근이 불가하도록 버킷의 퍼블릭 액세스를 제한하는 것이 필요하다.
사이트를 CloudFront 배포와 연결하면서 S3 버킷의 퍼블릭 액세스를 제한하기 위해선, 아래와 같은 두 방식을 취할 수 있다.
지금부터 두 방식을 모두 이용한 예제를 소개한다.
- 우선 S3 버킷의 퍼블릭 액세스를 차단하고, CloudFront 배포 구성 시 OAC(Origin Access Control) 정책을 추가하여 CloudFront를 통해서만 S3 오리진에 접근할 수 있도록 하는 방식
- 다만 배포 후에 페이지에서 브라우저 새로고침 동작 시, 4xx 에러가 발생하는 문제가 있어 이를 해결하기 위해 CloudFront Function 구성 필요
[ Amazon S3 > 버킷 > 버킷 만들기 ]
가장 먼저 버킷을 생성하는 것부터 시작한다. 이름과 리전만 설정해 주고 다른 옵션은 기본 값으로 두었다.
추가로, "이 버킷의 퍼블릭 액세스 차단 설정" 옵션에서 모든 퍼블릭 액세스 차단
체크박스가 체크 상태로 되어 있는지 확인해준다.
[ Amazon S3 > 버킷 > 버킷 선택 > 객체 업로드 ]
빌드 결과로 생성되었던 워크스페이스의 out/
디렉토리 내의 모든 파일을 빠짐 없이 업로드 해준다.
(수동으로 빌드 번들을 업로드 하는 대신에 이 과정을 자동화하여 업로드할 수도 있는데, 해당 방법에 대해선 다음 시리즈에서 다룰 예정이다.)
[ Amazon S3 > 버킷 > 버킷 선택 > 속성 ]
해당 방식에서는 정적 웹 사이트 호스팅 옵션을 활성화 할 필요가 없다. 따라서 옵션이 비활성화됨
으로 표시되는지 확인한다.
[ CloudFront > 배포 생성 ]
해당 화면에서 구성 가능한 주요 설정은 다음과 같다.
제어 설정 생성
버튼 클릭 후 팝업에서 생성Redirect HTTP to HTTPS
보안 보호 비활성화
(상황에 따라, 필요한 경우 활성화)index.html
입력
[ CloudFront > 배포 > 배포 선택 ]
배포를 생성하고 나면, 화면 상단에 아래 이미지와 같은 메시지가 노출된다.
메시지 우측에 정책 복사
버튼을 누른 뒤, [ 버킷 선택 > 권한 > 정책 ] 영역으로 이동하여 편집
버튼을 눌러 방금 복사한 정책을 붙여 넣고 저장해 준다.
// 예시
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<S3 bucket name>/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<AWS account ID>:distribution/<CloudFront distribution ID>"
}
}
}
]
}
[ CloudFront > 배포 > 배포 선택 > 일반 ]
세부 정보 영역을 보면 URL 형태로 된 배포 도메인 이름
이 생성된 것을 볼 수 있다. 브라우저를 열어 해당 도메인 경로로 이동하면, 우리의 웹 사이트가 제대로 보여지는 것을 확인할 수 있다. 라우트 이동도 정상적으로 동작하고 있(는줄 알았)다.
그런데 아래와 같이 새로고침을 하였더니 문제가 발생한다.
확인해 보니, 문제는 다음과 같은 경로에서 일어나는 걸로 정리되었다.
domain.cloudfront.net/
에서 새로고침: 정상 작동domain.cloudfront.net/some-path/
에서 새로고침하거나 해당 경로 직접 접근: 문제 발생, 페이지 불러올 수 Xdomain.cloudfront.net/some-path/index.html
경로 직접 접근: 정상 작동문제가 발생하는 원인과 해결하기 위한 방법을 찾아보았는데, Stack overflow - Next.js: How to make links work with exported sites when hosted on AWS Cloudfront? 답변에서 힌트를 얻을 수 있었다.
/
문자가 표시되면 일치하는 디렉토리 내부에서 index.html
을 제공함"이로 미루어 보았을 때, 현재 S3 버킷을 Website Endpoint로 사용하고 있지 않아서 발생하는 현상임을 알게되었다. 따라서 이 문제를 고치기 위해 Amazon CloudFront - 파일 이름이 포함되지 않는 요청 URL에 index.html 추가를 수행할 수 있다.
CloudFront Function에 대한 소개는 이 링크에서 확인 가능하다.
[ CloudFront > 함수 > 함수 생성 ]
function handler(event) {
var request = event.request;
var uri = request.uri;
// URI가 /로 끝나면 요청 경로 끝에 index.html 추가
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// URI가 /로 끝나지 않고 .을 포함하지 않으면 요청 경로에 /index.html 추가
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
작성 후에는 "게시" 탭을 눌러 함수 게시
버튼을 클릭해야만 배포와 함수를 연결할 수 있게 된다.
[ CloudFront > 배포 > 배포 선택 > 동작 ]
기존에 연결되어 있던 동작을 편집할 것이다. 동작 편집 화면의 하단에 있는 "함수 연결" 영역에서 뷰어 요청
에 방금 만든 함수를 연결시켜 준다.
설정 저장 후 배포가 완료될 때까지 잠시간 기다려 준다. 경험 상 수 분 정도 기다리면 완료되었다.
이제 다시 배포된 도메인에 접근하여 여러 라우트로 이동하고 새로고침을 마음껏 눌러도 페이지가 정상적으로 표시되는 걸 확인할 수 있다. 👍
- 해당 방식은 앞서 소개한 방식과는 다르게 별도의 CloudFront Function 구성 필요 X
- 대신 퍼블릭 액세스를 허용한 S3 버킷의 버킷 정책으로써 웹 사이트 요청에 특정 Referer 헤더가 있을 때만 액세스 할 수 있도록 하는 옵션을 추가하도록 하는 구성 필요
[ Amazon S3 > 버킷 > 버킷 만들기 ]
S3 버킷 생성은 첫 번째로 소개한 방식에서와 동일하게 생성해 준다. 지금 단계에서는 아직 퍼블릭 액세스를 허용하지 않아도 된다.
생성이 완료되면 이전에 했던 것처럼 빌드 파일 및 폴더 객체를 모두 업로드 해준다.
[ Amazon S3 > 버킷 > 버킷 선택 > 속성 ]
정적 웹 사이트 호스팅 옵션을 활성화
하도록 설정을 편집한다. 편집 후 저장하게 되면 아래와 같이 엔드포인트가 생성된다.
[ CloudFront > 배포 > 배포 생성 ]
웹 사이트 엔드포인트 사용
버튼 클릭<버킷명>.s3-website.<region>.com
)Referer
, 값 - 본인만 알 수 있는 값 설정[ Amazon S3 > 버킷 > 버킷 선택 > 권한 ]
CloudFront 배포의 오리진으로 S3 웹 사이트 엔드포인트를 사용하고 있습니다. 403 액세스 거부 오류가 발생하는 이유가 무엇인가요? 페이지에 안내된 내용에 따라, 아래와 같이 버킷 정책을 수정해 준다.
{
"Version":"2012-10-17",
"Id":"http referer policy example",
"Statement":[
{
"Sid":"Allow get requests originating from my CloudFront with referer header",
"Effect":"Allow",
"Principal":"*",
"Action":"s3:GetObject",
"Resource":"arn:aws:s3:::<S3 bucket name>/*",
"Condition":{
"StringLike":{"aws:Referer":"<MY_SECRET_TOKEN_CONFIGURED_ON_CLOUDFRONT_ORIGIN_CUSTOM_HEADER>"}
}
}
]
}
마지막으로, 모든 퍼블릭 액세스 차단 설정을 비활성화
해준다.
버킷이 퍼블릭 액세스가 가능하다고 표시되지만, 방금 버킷 정책에서 '특정 referer header가 전달된 경우에만 버킷에 접근 가능하도록 설정'하였기 때문에, 버킷의 웹 사이트 엔드포인트로 직접 접근하더라도 403 Forbidden
만 보여질 뿐이다.
[ CloudFront > 배포 > 배포 선택 > 일반 ]
이제 이전에 얻은 CloudFront 배포 도메인으로 접근해 보면 사이트가 정상적으로 보여짐을 확인할 수 있다.
- 배포와 Route 53을 연결하는 과정은 CloudFront를 Website Endpoint로 배포했든 REST API Endpoint로 배포했든 구성 방법이 동일
- 설명에서 사용하는 도메인은 기존에 만들어 두었던 도메인을 활용함
[ Route 53 > 호스팅 영역 > 레코드 생성 ]
레코드 이름, 레코드 유형, 트래픽 라우팅 대상을 아래와 같이 설정해 준다. 이 과정을 마치면 이제 정적 웹 사이트를 자신이 원하는 주소를 통해 제공할 수 있게 된다.
사실 배포 과정이 간단할 것이라고 생각했었는데, 예상과 다르게 난관에 부딪히게 되어 시간을 많이 잡아먹은 기억이 있다.
처음에는 Next.js로 정적 웹 사이트를 배포할 때 CloudFront + CloudFront Function 조합이 무조건적인 해결 방법인 줄 알았는데, 이번 포스팅을 작성하면서 여러 자료를 찾아보니 CloudFront Function을 이용하지 않고도 해결할 수 있다는 사실을 알게 되었다. 이로써 어떤 일이든 한 가지 방식으로만 해결하려고, 그 방식에만 매달리지 않아야 함을 느꼈다. 문제 해결에 있어 다양한 해결책을 보유하고 있으면 처한 상황에 따라 더 적절한 방식을 선택할 수 있으므로 여러모로 의미가 있는것 같다!
다음 포스트에는 Next.js 정적 웹 사이트 CI/CD 자동화 과정을 담아볼 예정이다.