이번 편의 결과물: 홈 화면(/)에 커스텀 Cache-Control 헤더를 적용해 재배포하면, 응답의 cache-control과 age 값이 설정한 시간대로 바뀝니다. · 다루는 개념: CloudFront TTL과 max-age/s-maxage 상호작용, Amplify 커스텀 헤더 설정, 정적 자산과 SSR 응답의 캐시 차이
04편에서 notice-board를 Amplify Hosting에 배포하고, 처음으로 x-cache·age·cache-control 헤더를 봤습니다. 지금까지 이 값들은 전부 Next.js가 알아서 정한 값이었습니다. 이 편에서는 Amplify 저장소 설정 파일로 그 값을 직접 바꿔봅니다. 아직 이 과목에는 홈 화면(/) 말고 다른 페이지가 없으므로, 이번 실습은 홈 화면을 대상으로 합니다.
이 편에서 만드는 파일
notice-board/
└── customHttp.yml (+, / 경로의 Cache-Control을 s-maxage=30으로 재정의)개념 정리
max-age와 s-maxage
| 지시어 | 대상 | 의미 |
|---|---|---|
| max-age | 브라우저 캐시 | 브라우저가 응답을 몇 초 동안 다시 요청하지 않고 재사용할지 |
| s-maxage | 공유 캐시(CDN 등) | CloudFront 엣지가 origin에 다시 묻지 않고 몇 초 동안 응답을 재사용할지. 같이 있으면 max-age보다 우선한다 |
CloudFront는 엣지에 캐시된 지 얼마나 지났는지를 age 응답 헤더로 알려줍니다. age가 s-maxage를 넘으면 다음 요청은 origin까지 다시 갑니다.
Amplify가 헤더를 정하는 방식
Amplify Hosting은 기본적으로 origin(Next.js 컴퓨트)이 보낸 Cache-Control을 그대로 CloudFront에 전달합니다. 커스텀 헤더를 따로 정의하면 그 값이 origin의 값을 대신합니다. 이 재정의는 상태 코드가 200 OK인 응답에만 적용되고, 에러 응답에는 적용되지 않습니다.
커스텀 헤더는 두 가지 방법으로 설정합니다.
| 방법 | 위치 | 특징 |
|---|---|---|
| Amplify 콘솔 | Hosting → Custom headers | 코드 변경 없이 즉시 설정, 저장 후 재배포 필요 |
| customHttp.yml | 저장소 루트 | Git으로 버전 관리됨. 콘솔 설정보다 우선 적용됨 |
이 편에서는 저장소에 넣어 버전 관리할 수 있는 customHttp.yml 방식을 씁니다.
pattern은 경로를 정확히 지정한다
pattern에 와일드카드 없이 /만 적으면 홈 화면 경로 하나에만 적용됩니다. 나중에 다른 경로까지 넓히려면 /api/*처럼 뒤에 *를 붙입니다. 지금은 홈 화면 하나만 다루므로 /로 좁혀서 씁니다.
정적 자산과 SSR 응답의 캐시 차이
| 응답 종류 | 기본 Cache-Control | 비고 |
|---|---|---|
_next/static/* 같은 불변 자산 | public, max-age=31536000, immutable | 파일명에 해시가 붙어 안전하게 영구 캐시 |
| 지금 시점의 홈 화면(완전 정적 렌더) | 매우 긴 s-maxage(사실상 1년) | 07편에서 fetch 기반 ISR로 바뀌면 값이 달라진다 |
| 동적으로 렌더된 페이지 | private, no-cache, no-store, max-age=0, must-revalidate | 사용자별 데이터가 섞이지 않도록 아예 캐시하지 않음 |
실습
1. 재배포 전 헤더 확인
curl -I https://main.d1234567890abc.amplifyapp.com/HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: s-maxage=31536000, stale-while-revalidate
x-cache: Hit from cloudfront
age: 342cache-control이 1년에 가까운 s-maxage로 잡혀 있고, x-cache가 Hit from cloudfront라 이미 CDN 엣지에 캐시된 상태입니다.
2. customHttp.yml 작성
# customHttp.yml
customHeaders:
- pattern: '/'
headers:
- key: 'Cache-Control'
value: 's-maxage=30'3. 커밋과 재배포
git add customHttp.yml
git commit -m "홈 화면 CDN 캐시 30초로 조정"
git pushGitHub 저장소에 푸시하면 04편에서 연결한 Amplify Hosting이 자동으로 새 빌드를 시작합니다. Amplify 콘솔 → 앱 선택 → 배포 이력에서 빌드가 끝났는지 확인합니다.
4. 재배포 후 헤더 확인
curl -I https://main.d1234567890abc.amplifyapp.com/HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: s-maxage=30
x-cache: Miss from cloudfront
age: 0재배포 직후 첫 요청은 CloudFront 입장에서 새 콘텐츠이므로 x-cache가 Miss from cloudfront, age는 0입니다. cache-control은 origin이 보낸 값 대신 customHttp.yml에 적은 s-maxage=30으로 바뀌어 있습니다.
30초 안에 같은 요청을 다시 보내면 x-cache: Hit from cloudfront와 함께 age가 경과 시간만큼(예: 12) 나오고, 30초가 지난 뒤 요청하면 다시 age: 0으로 리셋됩니다.
이 s-maxage=30 오버라이드는 07편에서 홈 화면에 60초 ISR을 적용할 때 그대로 두면 origin이 실제로 보내는 값을 가려버립니다. 07편 실습 전에 이 오버라이드를 제거하거나 값을 조정하는 단계가 포함되어 있으니, 지금은 그대로 둡니다.
직접 해보기
s-maxage값을5로 낮춰 재배포한 뒤, 5초 간격으로curl -I를 3~4번 반복해age가 자주0으로 리셋되는 것을 관찰해보세요.pattern을/에서/no-such-page처럼 실제로 없는 경로로 바꿔 재배포하면, 홈 화면의cache-control이 다시 원래 값(1년에 가까운 s-maxage)으로 돌아오는지 확인해보세요.
정답 보기
s-maxage=5로 재배포한 뒤 5초보다 짧은 간격으로 요청하면 age가 0, 1, 2처럼 쌓이다가, 5초를 넘긴 요청에서 다시 age: 0으로 리셋되며 x-cache도 Miss from cloudfront 또는 RefreshHit from cloudfront로 바뀝니다. pattern이 실제 요청 경로와 일치하지 않으면 그 커스텀 헤더는 어떤 응답에도 적용되지 않으므로, 홈 화면은 origin이 원래 보내는 값으로 돌아갑니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
| customHttp.yml을 고쳤는데 헤더가 그대로임 | 재배포를 안 함 | 커밋 후 push해서 Amplify 자동 배포를 트리거한다 |
| 404 같은 에러 페이지에도 캐시 헤더가 적용될 것으로 기대 | Amplify는 200 OK 응답에만 커스텀 Cache-Control을 적용 | 에러 캐싱이 필요하면 별도 pattern으로 상태 코드를 분리해 다룬다 |
| 콘솔에서 설정한 값과 customHttp.yml 값이 달라 헷갈림 | 저장소의 customHttp.yml이 콘솔 설정보다 항상 우선 적용됨 | 두 곳을 동시에 쓰지 말고 한쪽만 유지한다 |