정적(static) 웹 사이트를 배포하기 위해 별도의 웹 서버를 구축하기보다 AWS S3와 CloudFront를 이용하는 경우가 많이 있습니다. 특히, 요즘에는 React나 Vue.js를 통해 SPA(Single Page Application) 형태로 웹 사이트를 만드는 것이 일반적입니다.
SPA 형태의 웹 사이트는 Path 단위로 동작하며, 각 Path가 요청되었을 때 당연히 그 경로에 있는 index.html이 응답할 것이라고 가정하고 있습니다. 그리고 S3의 “정적 웹 사이트 호스팅” 기능을 통해 SPA를 실행하면 아무런 에러 없이 잘 동작합니다. 그런데 CloudFront를 연동하게 되면 403 에러 페이지가 출력되면서 사람을 당황스럽게 만듭니다.
이 글에서는 이러한 상황에서 조치할 수 있는 방법을 설명드리겠습니다.
[방법 1] CloudFront 오류 페이지 설정 (간단하지만 완전하지는 않은)
구글링 하면 흔하게 찾을 수 있는 방법입니다. 매우 간단하면서도 대부분의 경우에는 더 이상의 조치가 필요하지 않은 방법입니다. CloudFront의 배포를 선택한 후 “오류 페이지” 탭에서 “사용자 정의 오류 응답 생성” 버튼을 클릭합니다. 그리고 403 오류에 대해 “/index.html” 페이지를 200 OK로 응답하도록 설정합니다. 이게 끝입니다. 참 쉽죠?
아직 해결되지 않은 문제
SPA를 정말 Single로만 구현하여 배포한 경우에는 아마 더 이상의 조치는 필요하지 않을 것입니다. 그런데 세상 일이라는 것이 그렇게 간단하지가 않습니다. 여러 개의 SPA를 만들고 하나의 도메인으로 서비스하는 상황도 있을 수 있습니다. 각각의 기능이 너무 커서 분리하여 개발할 수도 있고 담당하는 조직이 달라서 분리하여 개발할 수도 있습니다.
그렇게 만들어진 여러 개의 SPA 중 메인이 되는 “SPA 1″을 S3에 업로드하고 그 하위 디렉토리에 다른 “SPA 2″를 업로드하는 상황을 가정해 보겠습니다. 이렇게 두 개의 SPA를 배포하였을 때 “SPA 1″은 아무런 문제 없이 잘 동작하는데 “SPA 2″가 의도와 다르게 동작하거나 에러가 발생할 가능성이 매우 큽니다. 이런 상황을 만나게 되더라도 “SPA 2” 개발자를 찾아가서 문제를 해결하라고 닦달하지는 마세요. 그분은 잘못이 없습니다.
“SPA 2″에서 문제가 발생하는 이유는 바로 위의 [방법 1]과 같이 설정했기 때문입니다. “SPA 2″를 실행하기 위해 Path를 요청하면 CloudFront에서는 403 에러가 발생하게 되고 대체 페이지로 “/index.html”를 응답하게 됩니다. 여기서 앞에 붙어 있는 슬래시(/)가 문제의 원인입니다. 다시 말해, “SPA 2″의 index.html이 아닌 루트에 있는 “SPA 1″의 index.html 페이지가 응답하게 됩니다.
이러한 상황에서는 오류 페이지 설정만으로는 문제를 해결할 수 없으므로 다른 방법을 설명드리겠습니다.
[방법 2] CloudFront 함수 연결
다음과 같은 순서로 CloudFront 함수를 만들고 배포에 연결해 보세요.
(1) CloudFront 좌측 메뉴에서 “함수” 선택 → “함수 생성”
(2) “개발” 탭에 있는 코드를 모두 지우고 아래의 코드를 복사&붙여넣기 후 “변경 사항 저장” → “게시” 탭에서 “함수 게시” 버튼 클릭
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; }
(3) CloudFront 배포 선택 후 “동작” 탭에서 항목을 선택하고 “편집” 버튼 클릭 → “함수 연결”의 “뷰어 요청”에서, 함수 유형을 “CloudFront Functions”로 선택하고 함수 ARN/이름을 위에서 만든 함수 이름으로 선택
마무리
몇 달 전에 이 문제를 처음 만났을 때 많이 당황했던 기억이 있습니다. 문제의 원인을 찾는 데만도 이틀이나 걸렸습니다. 문제 해결을 위해 Lambda@Edge를 써야 되나 고민하던 차에 CloudFront Functions가 얼마 전(2021년)에 생겼다는 것을 알게 되었고, Functions의 예제 중 하나로 제가 겪은 문제를 해결할 수 있었습니다.
여러분도 삽질 그만하고 빨리 퇴근합시다!! (이렇게 적긴 했지만 이 글은 퇴근하고 집에서 쓴 글입니다.)