프로덕션 CPU가 가끔 스파이크를 칠 때가 있습니다.
귀여운 건 아니고요. 장비가 아니라 사람 심박수가 올라가는 타입입니다.
웹뷰 앱도 "느려요"라고 말은 안 하는데, LCP가 말해 줍니다.
그래서 이번에 /_next/static을 집(앱 서버)에서 쫓아내서 S3랑 CloudFront에 월세 내고 살게 만들었습니다.
| 질문 | 답 |
|---|---|
| 뭘 했냐 | 정적 파일은 CDN, HTML·데이터는 예전 그대로 앱 |
| 왜 하냐 | CPU가 번들 배달부까지 뛰는 건 좀 심했다 |
| S3는 그냥 열어두냐 | 아니요. OAC로 "이 CloudFront만 들어와" 모드 |
NEXT_STATIC_ASSET_PREFIX를 주면 Next.js가 정적 자산 URL의 집 주소를 앱이 아니라 CloudFront로 찍습니다.
assetPrefix가 하는 일, 한 줄로 말하면 "이제 JS/CSS는 여기서 받아" 스티커 붙이기입니다.
CI가 .next/static을 S3에 sync하고, CloudFront에 /_next/static/* 무효화를 날립니다.
캐시는 착한 아이도, 나쁜 아이도 아니고 설명을 안 들으면 오해하는 아이입니다. 그래서 invalidation이 필요합니다.
사용자(WebView 포함) 입장에서는 문서·데이터는 앱으로 가고, 번들·CSS·폰트는 가까운 PoP로 갑니다.
S3는 인터넷에 대문 활짝 열지 않고, OAC로 "우리 집은 이 배달앱(CloudFront) 주문만 받아요" 컨셉입니다.
정적 파일 전송이랑 Range 요청 같은 잡일이 오리진에서 빠지면, 같은 트래픽이라도 앱 CPU가 덜 빡치는 쪽으로 기울기 마련입니다.
main 푸시)

main 푸시 → GitHub Actions가 next build (여기서 운명이 갈립니다)aws s3 sync로 _next/static 업로드 — 보통 immutable + 긴 max-age 조합cloudfront create-invalidation으로 /_next/static/* — "옛 해시는 이제 그만" 선언GET .../chunks/해시.js → miss면 S3에서 가져오고, 이후엔 hit로 엣지에서 씽씽| 구분 | 내용 |
|---|---|
| CDN | assetPrefix + CI에서 .next/static → S3 sync + /_next/static/* 무효화 |
| CloudFront / S3 | OAC + 버킷 정책으로 CloudFront만 읽기 |
| Docker | 프로덕션 이미지 prebuilt standalone 조립 |
| Next 이미지 | remotePatterns에 CloudFront 호스트 추가 (안 하면 Next가 거절합니다. 까다로운 친구예요.) |
| 빌드 안정성 | CDN 단계에서 Node OOM 나지 않게 옵션 |
| 문서 | docs/deploy-prod-static-cdn-checklist.md — 배포 후 "진짜 됐나" 체크 |
CDN 붙인다고 세상이 다 바뀌진 않지만, 캐시 무효화·OAC·이미지 허용까지 같이 가면 "왜 이상해요?"가 "아 그거죠"로 짧아집니다. 그리고 이번 구조에서는 서버가 덜 바쁜 쪽으로 기울기 쉽습니다. 정적 파일(번들·CSS·폰트 등)을 CloudFront·S3 쪽으로 넘기면, 같은 트래픽이라도 오리진(Next 컨테이너·앞단 nginx)이 들고 뛰어야 할 바이트와 동시 요청이 줄어듭니다. 그래서 서버 부하는 내려가고, 예전에 튀던 CPU 스파이크도 덜 튀는 방향이 됩니다. (정적 전송·Range 요청 같은 잡일이 앱 프로세스에서 빠지니까요.)
체감 성능 쪽으로는 LCP·FCP가 나아진다는 식으로 잡히는 경우가 많고, 웹뷰처럼 네트워크·메인 스레드에 민감한 환경에서는 그 차이가 더 잘 드러납니다.
끝.