기존에는 웹 최적화라고 하면 그동안은 프론트엔드 코드에서 할 수 있는 것들만 생각했었다.
JS 번들 크기를 줄인다든지, 이미지 압축을 한다든지, 코드 스플리팅으로 초기 로딩 속도를 개선한다든지.
이번 과제를 하면서 처음으로 인프라 레벨에서 성능 최적화가 얼마나 큰 차이를 만들어낼 수 있는지 직접 체감할 수 있었다.
S3
에 올린 정적 파일과 CloudFront
를 통한 CDN
구조의 차이를 눈으로 확인해보니, 같은 코드를 어떻게 전달하느냐에 따라 사용자 경험이 크게 달라진다는 걸 배울 수 있었다.
이번 글에서는 S3
와 CloudFront
기반의 정적 사이트 배포 과정과, 직접 측정한 성능 차이 분석을 중심으로 그 과정에서 새롭게 알게 된 것들을 정리해보려고 한다.
이번 과제에서는 GitHub Actions
를 이용해 정적 사이트 배포 파이프라인을 구성하는 것이 첫 단계였다.
평소에는 Vercel
같은 플랫폼에 자동 배포를 맡기는 경우가 많아서, 직접 CI/CD 워크플로우를 작성해보는 경험은 처음이었다.
워크플로우는 기본적으로 코드 푸시
→ 빌드
→ S3 업로드
→ CloudFront
캐시 무효화 순서로 구성했다.
name: Deploy Next.js to S3 and invalidate CloudFront
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Deploy to S3
run: |
aws s3 sync out/ s3://${{ secrets.S3_BUCKET_NAME }} --delete
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"
단계별로는 단순해 보여도 처음에는 어떤 명령어를 언제 실행해야 하는지, S3
와 CloudFront
가 어떤 식으로 연결되는지 개념을 잡는 데 시간이 좀 걸렸다.
특히 aws cloudfront create-invalidation
명령어를 사용해 캐시 무효화를 따로 해줘야 최신 빌드가 사용자에게 바로 반영된다는 점은 이번에 처음 알게 된 부분이었다.
그동안은 플랫폼에서 알아서 해주던 과정을 직접 구성하면서 배포 자동화에서 어떤 부분이 중요한지 한 단계 더 깊이 이해할 수 있었다.
이번 파이프라인을 구성하면서 가장 먼저 고민했던 건 정적 사이트 배포라는 특성상 어떤 흐름이 가장 안정적일까였다.
Next.js
를 next export
로 빌드하면 서버가 필요 없는 순수 정적 파일들이 나오기 때문에 S3
에 정적 파일을 저장하고, CloudFront
를 앞단에 두어 CDN
으로 빠르게 전달하는 구조가 가장 적합하다고 판단했다.
또, GitHub Actions
로 자동화를 구성한 이유는 단순한 CLI 배포보다 반복성과 안정성이 높기 때문이었다.
수동으로 빌드하고 CLI로 배포하는 경우엔 사람이 실수할 여지가 많은데, CI/CD
파이프라인을 구성해두면 코드 변경과 배포가 항상 같은 흐름으로 진행되기 때문에 유지보수도 더 용이해진다.
마지막으로 CloudFront
캐시 무효화 단계까지 넣은 이유는, 정적 사이트는 기본적으로 캐시가 강하게 적용되기 때문에 업데이트 시점에 사용자가 이전 버전 파일을 보는 문제가 발생할 수 있어서였다.
처음에는 왜 invalidate
가 필요한지 몰랐는데, 실제로 캐시가 적용된 상태에서 변경한 파일이 바로 반영되지 않는 걸 확인하고 나서야 캐시 무효화의 필요성을 체감할 수 있었다.
이번 테스트는 Chrome DevTools
의 네트워크 탭을 활용해 직접 측정했다.
같은 Next.js
프로젝트를 next export
후 S3
정적 웹사이트 호스팅과 CloudFront
배포를 통해 서비스하고, 두 환경에서 동일한 페이지에 대해 최초 요청 시점과 반복 요청 시점을 비교했다.
네트워크 상태는 기본값(Fast 3G, Slow 4G 등)을 따르지 않고 실제 내 네트워크 환경에서 측정했으며, 요청 간 브라우저 캐시를 비우고 진행했다.
항목 | S3 배포 | CloudFront 배포 | 비교 (감소율) |
---|---|---|---|
Finish | 256ms | 203ms | 약 20.7% 감소 |
DOMContentLoaded | 101ms | 76ms | 약 24.8% 감소 |
Load | 222ms | 183ms | 약 17.6% 감소 |
Transferred | 487KB | 205KB | 약 57.9% 감소 |
Resources | 481KB | 481KB | 동일 |
문서 파일 (HTML) | 13.4KB (54ms) | 3.4KB (38ms) | 크기 74.6%, 시간 29.6% 감소 |
CloudFront 배포는 전반적으로 전송 속도와 데이터 크기 측면에서 더 우수한 성능을 보였다. 특히 HTML 문서의 크기와 전송 속도 개선이 뚜렷하게 드러났다.
가장 큰 차이는 다음과 같다.
캐싱 위치
CloudFront
는 사용자와 가까운 엣지 로케이션에서 응답한다.
반면 S3
는 특정 리전의 S3
버킷에서 직접 응답하므로 물리적 거리에서 오는 네트워크 지연이 발생한다.
압축 적용 여부
CloudFront
는 기본적으로 Gzip
압축이 적용된다.
반면 S3
정적 웹 호스팅은 Content-Encoding
헤더가 없어 원본 그대로 전송된다.
반복 요청 최적화
CloudFront
는 동일 요청에 대해 엣지 서버 캐시에서 빠르게 응답할 수 있고, 재전송 시 더 빠른 속도를 보여준다.
S3
는 매번 버킷에서 원본을 내려준다.
첫 페이지 로드 개선: 첫 로드에서 약 20% 이상의 속도 차이를 체감할 수 있다.
모바일 환경에서의 데이터 절감: Transferred
크기가 절반 이하로 줄면서 모바일 데이터 사용량이 줄어든다.
반복 요청에서 UX 개선: 엣지 캐싱으로 인해 페이지 이동이나 새로고침 시 반응 속도가 더 빠르다.
트래픽 비용 절감 가능성: 전송량이 줄면 AWS 트래픽 비용에도 긍정적 영향을 준다.
사실 빌드 결과물은 S3
와 CloudFront
모두 동일한 정적 파일이기 때문에, 처음에는 네트워크 속도에 큰 차이가 나지 않을 거라고 생각했다.
'파일 크기만 같으면 전송 시간도 비슷하겠지' 정도로 가볍게 생각했는데, 네트워크 탭에서 Transferred
크기와 응답 시간이 예상보다 많이 달라져서 놀랐다.
특히 S3
에서는 압축이 적용되지 않고 원본 크기로 내려갔던 반면, CloudFront
에서는 자동으로 Gzip
압축이 적용돼 전송량 자체가 크게 줄어드는 걸 확인할 수 있었다.
그 결과 DOMContentLoaded
시점과 전체 Load
시점 모두 눈에 띄게 개선됐고, 같은 파일이라도 '어디에서 어떻게 전송하느냐'가 사용자 경험에 얼마나 영향을 주는지를 실제로 체감할 수 있었다.
이번 과제를 진행하면서 웹 최적화라는 게 꼭 코드 수준에서만 이루어지는 건 아니라는 점을 처음으로 경험할 수 있었다.
지금까지는 코드 압축, 이미지 최적화, 코드 스플리팅 같은 프론트엔드 레이어의 최적화에만 익숙했는데, 이번에는 같은 파일이라도 인프라 레벨에서 어떻게 전송하느냐에 따라 사용자 경험이 크게 달라질 수 있다는 사실을 눈으로 확인할 수 있었다.
특히 S3
와 CloudFront
의 성능 차이를 측정하면서, 전송 경로와 네트워크 최적화가 사용자에게 체감되는 속도에 직접적인 영향을 준다는 것이 가장 인상 깊었다.
압축 적용 여부, 캐시 위치, 반복 요청 처리 방식 같은 요소들이 생각보다 큰 차이를 만들어냈고, '같은 파일이라도 어디에서 어떻게 전달하느냐'가 중요한 최적화 포인트라는 걸 새롭게 배웠다.
이번 경험을 통해 한편으로는 새로운 궁금증도 생겼다.
Vercel
이나 Netlify
같은 플랫폼들은 이런 최적화를 내부적으로 어떻게 처리하고 있을까? 별도의 CDN 설정 없이도 빠른 응답 속도를 제공하는데, 내부적으로 어떤 구조와 전략이 적용되고 있는지 궁금해졌다.
또한 CDN
을 사용하지 않고도 비슷한 최적화를 할 수 있는 방법은 무엇이 있을까 하는 생각도 들었다.
그리고 무엇보다 중요한 질문은, 'CDN이 항상 최선의 선택일까?' 라는 점이었다.
실시간성이 중요한 서비스에서는 캐싱이 오히려 문제가 될 수도 있다.
이런 경우에는 어떤 인프라 아키텍처가 더 적합할지, 어떤 상황에서 CDN
을 쓰고 어떤 상황에서는 다른 전략을 선택해야 할지에 대한 고민이 앞으로 더 필요할 것 같다.
이번 경험을 통해 프론트엔드 개발이라고 해서 꼭 브라우저 렌더링이나 코드 최적화에만 갇혀 있을 필요는 없겠다는 생각이 들었다.
결국 사용자가 느끼는 속도는 네트워크, 서버, 전송 구조까지 포함한 전반적인 시스템 설계에 영향을 받기 때문에, 앞으로는 이런 인프라적인 부분도 더 이해하고 활용해보고 싶다.