슬슬 다른 프로젝트 시작하면서, 여유 시간이 나면 제작기를 조금씩 연재해보려 해요.
사실 이 문제를 해결한 건 꽤 됐는데, 거의 반나절이 걸려 해결했던 에러였어요.
- 각 페이지를 인덱스로부터 들어가는 건 정상으로 나옵니다.
- 인덱스에서 새로고침하는 것 역시 정상입니다.
3. 그런데... 다른 페이지에서 새로고침을 하면 403 에러가 발생합니다. (혹은index.html
에서의 레이아웃이 렌더링됩니다.)AWS CloudFront
에서 리다이렉트를 설정하지 않았는지를 살펴보았지만 이미 잘 설정했습니다. (먼저 CloudFront에서의 리다이렉트를 확인해주세요.)- 404가 나와야할 곳에서는 403에러가 발생합니다.
아니! 당시에 저는 왜 계속해서 403에러가 발생하는지를 도저히 모르겠더라구요. 😖
그리고 자정부터 새벽 6시까지 밤샘 삽질 끝에 얻은 결론은, 제가 정적 배포 후 렌더링되는 원리를 제대로 이해하지 못해서 발생한 문제였어요.
이에 대해서 삽질하고 있을 누군가에게 6시간을 덜어드리기 위해, 글을 작성하게 됐어요.
그럼, 그 이유를 탐색해볼까요?
이게 가장 이해가 되질 않았어요.
index.html
이 되는데 왜 다른 [경로 이름].html
은 되지 않을까요?
심지어 CSR
이 되어 페이지 이동이 가능하고 있기 때문에 저는 몇 시간 동안 이 문제 정의를 다음과 같이 내렸어요.
삐빅! 틀렸습니다 😖
그도 그럴 것이, CSR
로 다른 페이지 이동이 되는데 새로고침 시 폴백이 403에러가 난다는 것은, SSG로 정적인 제너레이션이 되지 않는다는 의미에요. .html
그래서 버킷을 살펴보니... 너무나 열받게도, 버킷의 루트에 잘 있었어요.
그렇게, 30분이면 해결될 것 같던 일은... 골치 아프게도 무한으로 이어졌습니다.
삐빅! 정답입니다 🎉
실제로 [URL]/[라우트]
에 대한 .html
은 어디서 받아올 수 있을까요?
바로 [S3 Bucket]/[Route]
에서 가져올 수 있습니다.
예컨대, URL/shop
이라는 경로라면, S3 버킷에는 shop/index.html
이라는 경로로 존재하면 됩니다. 따라서 수동으로 AWS S3에 옮긴 결과, 동작함을 확인했습니다! 🙆🏻
이는 AWS S3 객체 요청에 따른 정상적인 응답입니다. S3에서 객체를 이용하여 정적인 사이트를 생성하게 된다면, 원래라면 나왔어야 할 404에러가, 원하는 객체를 찾지 못했기 때문에 액세스 거부 오류로 403 에러를 생성하는 거죠.
이에 관련해서는 Amazon S3의 403 액세스 거부 오류 문제를 해결하려면 어떻게 해야 합니까?을 참고해주세요!
페이지가 작으니, 그냥 cp
와 mv
명령어를 추가해서 일일이 입력해볼까란 생각도 들었어요.
그런데 문제가 있습니다.
만약 라우트 주소가 많아지거나, 나중에 변경할 일이 생긴다면?
퍼블릭에서 뱉은 디렉토리의 이름과 중복되어 꼬이는 문제가 발생한다면?
뭔가 나중에는 정말 오류를 못찾을 수도 있을만큼 배포과정에 있어서 복잡도가 올라간다는 느낌이 들었습니다.
지금은 주기적으로 포트폴리오 프로젝트를 업데이트하지만, 나중에 3년 후 갑자기 이걸 수정할 때에도 이 주의사항들을 염두해야 한다는 것이 스트레스를 받았어요.
즉, 자동화는 할 수 있지만 안정성에 대한 문제가 발생했습니다.
따라서 이미 답은 나왔지만 시원하지 않아서... 6시간을 삽질했습니다. 😭😭
.html
을 제거하자.결국 특이한 방법으로 문제를 해결했는데요. 약 4시간의 삽질 시도와 서칭 끝에, 저와 비슷한 고민을 하던 분의 글의 도움을 받을 수 있었어요.
이 분의 해결 방법은, .html
을 제거하는 방법이었어요.
오호! 뭔가 좀 이상한 느낌은 들었지만, 납득이 갔어요.
이 리소스에 대한 타입은 결국
Content-Type
으로 알려주면 되는 거 아냐?
따라서 이를 위해 다음과 같이 적용했습니다.
- name: delete all files
run: aws s3 rm ${{ secrets.AWS_S3_BUCKET_NAME }}/ --recursive
- run: aws s3 sync ./out/_next/ ${{ secrets.AWS_S3_BUCKET_NAME }}/_next/
- name: remove .html files extension
run: for file in $(find ./out -name "*.html"); do mv "$file" "${file%%.html}"; done
그 다음에, 확장자가 제거된 html
파일을 주면 되겠죠?
이후에는, 저는 이 파일들만 text/html
이라는 힌트를 주며 동기화를 시켜줍니다.
- name: Set HTML Content-Type and Copy them
run: aws s3 sync ./out ${{ secrets.AWS_S3_BUCKET_NAME }} --exclude "*.*" --content-type "text/html" --acl public-read --delete
이해 되셨나요?
즉, 만약 extension을 위한 점 표기가 없다면 이를 동기화하라는 의미입니다.
그렇다면, 이제 나머지 퍼블릭 디렉토리에 있는 asset들을 가져와야 합니다.
다음과 같이 말이죠!
- name: Copy Other Assets
run: aws s3 sync ./out/ ${{ secrets.AWS_S3_BUCKET_NAME }} --exclude "*" --include "*.jpg" --include "*.png" --include "*.svg" --include "*.ico" --include "*.mp4" --include "*.webp" --metadata-directive REPLACE --cache-control max-age=600
여기서도 또 삽질이 발생했는데, 저는 --include
의 옵션을 regex expression
으로 지정했었는데, 이는 잘못된 지정이더라구요!
AWS S3 CLI에서는 정규표현식을 지원하지 않고, 다음 패턴 심볼만 가능합니다.
*: Matches everything
?: Matches any single character
[sequence]: Matches any character in sequence
[!sequence]: Matches any character not in sequence
이후 저는 약 10분 동안은 캐시가 유효하도록 캐시 컨트롤을 지정해주었는데, 이는 원하는대로 수정하시면 됩니다.
안타깝게도 확장자가 많아지면 일일이 작성해야 한다 단점은 분명 존재하지만, 원인을 찾기에는 더 빠를 것 같다는 확신이 들었습니다. (내가 include
를 하지 않았다는 오류에 대한 인지 사고가 어렵지는 않기 때문이죠!)
그렇게 문제는 해결됐어요!
사실 이 문제를 해결할 때, SSG
로 AWS
에 배포한 건 처음인지라 더 많은 삽질이 발생했어요.
그렇지만, 결국 이를 해결하는 과정 속에서 더 많은 것을 배웠습니다. (정말 많이요! 🙆🏻)
결국, 헤매는 만큼 자기 땅인 것 같아요. 😉
이 글이 AWS S3라는 바다에서 SSG로 인해 헤매는 누군가에겐 도움이 되길 바라며. 이상! 🌈