S3 + CloudFront 환경에서 SPA를 운영할 때 마주치는 캐시 일관성과 클라이언트 사이드 라우팅 fallback 문제를 체계적으로 해결한 설계 사례다. Vite 빌드 결과물의 파일 구조 특성을 바탕으로 Cache-Control 정책을 파일 유형별로 분리하고, CloudFront Function으로 404를 index.html로 리다이렉트하는 전략을 다룬다.
핵심 포인트- `index.html`은 `Cache-Control: no-cache`로, 해시 포함 에셋(`main.a1b2c3.js`)은 `max-age=31536000, immutable`로 분리
- CloudFront의 기본 403/404 에러 응답을 `index.html`로 커스텀해 SPA 라우팅 보장
- CloudFront Function을 사용해 URL 경로 패턴에 따라 요청을 S3 키로 재매핑
- 배포 시 `index.html` 교체 순서: 에셋 먼저 업로드 후 `index.html` 교체 — 순서가 반대면 구 클라이언트가 없는 파일 요청
- CloudFront Invalidation 비용과 범위를 최소화하기 위해 `index.html`만 선택적 무효화
상세 정리- SPA 배포의 핵심 문제: 브라우저가 오래된 `index.html`을 캐시하면 신규 에셋 해시를 참조하지 못해 로딩 실패
- Vite 빌드 산출물 구조: `index.html`(변하지 않는 진입점) + `/assets/*.{js,css}`(내용 해시 포함 불변 파일)
- Cache-Control 전략 설계: S3 메타데이터 + CloudFront 응답 헤더 정책 두 레이어를 함께 설정해야 실효
- SPA fallback 구현: CloudFront 에러 페이지 설정에서 404→200 응답 코드로 `index.html` 반환
- CloudFront Function 활용: `/api/*` 패턴은 ALB 오리진으로, 나머지는 S3 오리진으로 라우팅 분기
- 배포 스크립트 순서 보장: `aws s3 sync --exclude index.html` 후 `aws s3 cp index.html` 로 원자적 전환 흉내
- Invalidation 전략: `/*` 전체 무효화 대신 `/index.html`만 무효화해 캐시 히트율 유지
- 실수 사례 공유: 에셋 먼저 무효화하면 구 `index.html`이 없는 파일을 참조하는 공백 시간 발생
- Vite `base` 옵션과 S3 키 prefix 정렬 주의사항: CloudFront 배포 경로와 Vite 빌드 base가 다르면 에셋 경로 불일치
왜 읽나S3 + CloudFront SPA 배포는 흔하지만 캐시·fallback 함정은 반복해서 마주치는 문제다. 이 글은 실수 사례와 순서가 중요한 배포 스크립트까지 포함해 실무 체크리스트로 활용하기 좋다.