AWS 실전 운영 시리즈 1/12
React로 만든 관리자 페이지를 배포할 때 꼭 애플리케이션 서버가 필요한 것은 아니다.
빌드가 끝난 React SPA는 결국 HTML, JavaScript, CSS, 이미지 같은 정적 파일이다. 이 파일들은 Node 서버나 Nginx가 없어도 S3와 CloudFront만으로 충분히 서빙할 수 있다.
하지만 "S3에 올리면 끝"이라고 생각하면 운영 중에 여러 문제를 만난다.
HTTPS, 커스텀 도메인, SPA 라우팅, 캐시 정책, 배포 후 무효화, 데이터 파일 보호 같은 것들을 미리 정해야 한다.
문제 상황
운영 콘솔은 React SPA로 만들었다.
사용자는 많지 않았지만, 내부 업무에 매일 쓰이는 화면이었다. SEO는 중요하지 않았고, 서버 사이드 렌더링도 필요하지 않았다.
이런 조건에서는 프론트엔드 서버를 따로 운영하는 것이 과해 보였다.
선택지는 대략 세 가지였다.
- EC2에 Nginx를 올려 정적 파일 서빙
- 외부 호스팅 서비스를 사용
- S3와 CloudFront로 정적 호스팅
최종적으로 S3와 CloudFront를 선택했다.
React build 산출물의 성격
React SPA를 빌드하면 보통 build/ 디렉터리가 만들어진다.
그 안에는 정적 파일이 들어 있다.
build/
├── index.html
├── static/js/main.js
├── static/css/main.css
└── assets/...이 파일들은 요청을 처리하는 서버 로직을 필요로 하지 않는다.
브라우저가 index.html을 받고, JavaScript가 실행되면서 화면과 라우팅을 처리한다.
따라서 정적 파일 저장소와 CDN만으로도 배포할 수 있다.
S3 단독으로 충분하지 않은 이유
S3는 정적 파일을 저장하고 제공할 수 있다.
하지만 운영 서비스의 앞단으로 S3만 두기에는 아쉬운 점이 있다.
- HTTPS와 커스텀 도메인 구성이 제한적이다.
- CDN 캐시 정책을 세밀하게 다루기 어렵다.
- 경로별 정책을 나누기 어렵다.
- S3 bucket을 직접 공개해야 할 수 있다.
- SPA fallback 처리가 깔끔하지 않을 수 있다.
그래서 S3는 origin으로 두고, 사용자는 CloudFront를 통해 접근하게 하는 구조가 더 좋다.

CloudFront는 엣지, HTTPS, 캐시, 경로 정책을 담당한다.
S3는 파일 저장소 역할에 집중한다.
이 구조에서 중요한 점은 모든 파일이 같은 성격이 아니라는 것이다. index.html, hash가 붙은 정적 자산, 운영 JSON은 같은 bucket에 있어도 CloudFront에서는 다른 정책으로 다뤄야 한다.
먼저 정해야 할 것들
S3와 CloudFront로 React SPA를 배포하기 전에 다음을 정해야 한다.
1. 파일을 어디에 올릴 것인가
정적 파일과 데이터 파일을 같은 bucket에 둘 것인지, 분리할 것인지 정해야 한다.
같은 bucket에 둔다면 배포 스크립트에서 데이터 경로를 덮어쓰지 않도록 조심해야 한다.
예를 들어 다음 경로는 성격이 다르다.
build/ # React 정적 파일
data/ # 운영 데이터 JSON
latest/ # 최신 데이터 캐시정적 파일 배포가 데이터 파일을 삭제하면 안 된다.
2. 커스텀 도메인을 쓸 것인가
운영 콘솔은 보통 사용자가 기억하기 쉬운 도메인이 필요하다.
예를 들어 공개 글에서는 다음처럼 일반화할 수 있다.
console.example.comCloudFront에 커스텀 도메인을 연결하려면 인증서가 필요하다.
CloudFront에서 쓰는 ACM 인증서는 리전 조건도 주의해야 한다. 내부 문서에는 인증서 위치와 도메인 연결 방식을 명확히 남겨야 한다.
3. SPA 라우팅 fallback을 어떻게 할 것인가
React Router를 쓰는 SPA에서는 /reports, /settings 같은 경로를 브라우저가 직접 요청할 수 있다.
하지만 S3에는 /reports/index.html 파일이 없다.
따라서 CloudFront 또는 S3에서 404/403 응답을 index.html로 돌려주는 설정이 필요하다.
이 설정이 없으면 새로고침할 때 화면이 깨진다.
4. 캐시 정책을 어떻게 나눌 것인가
모든 파일에 같은 캐시 정책을 적용하면 안 된다.
정적 JS/CSS 파일은 길게 캐시해도 된다. 파일명에 hash가 붙기 때문이다.
하지만 index.html은 너무 오래 캐시되면 새 배포가 반영되지 않을 수 있다.
JSON 데이터 파일은 더 조심해야 한다. 운영 데이터가 바뀌는데 캐시 때문에 이전 데이터가 보일 수 있다.
따라서 파일 성격별로 캐시 전략을 나눈다.
| 파일 | 캐시 전략 |
|---|---|
| hashed JS/CSS | 길게 캐시 |
index.html |
짧게 캐시 또는 invalidation |
| 운영 JSON | 짧은 TTL |
| 감사 백업 파일 | 직접 화면에서 읽지 않으면 캐시 중요도 낮음 |
5. 배포 후 invalidation을 할 것인가
CloudFront는 캐시된 파일을 계속 제공할 수 있다.
그래서 배포 후 즉시 반영이 필요하면 invalidation을 실행해야 한다.
aws cloudfront create-invalidation --distribution-id DISTRIBUTION_ID --paths "/*"항상 전체 invalidation이 정답은 아니다. 하지만 내부 운영 콘솔처럼 배포 빈도가 높지 않고 즉시 반영이 중요하다면 단순한 전체 무효화가 실용적일 수 있다.
EC2 + Nginx를 선택하지 않은 이유
EC2에 Nginx를 두는 방식도 가능하다.
하지만 정적 파일만 서빙하기 위해 서버를 운영하면 다음 책임이 생긴다.
- 인스턴스 운영
- OS 패치
- Nginx 설정
- 장애 대응
- 스케일링
- 인증서 갱신
작은 운영 콘솔에서는 이 책임이 과할 수 있다.
S3와 CloudFront를 쓰면 정적 파일 서빙에 대한 서버 운영 부담을 줄일 수 있다.
외부 호스팅 서비스를 선택하지 않은 이유
Vercel이나 Netlify 같은 서비스도 좋은 선택이다.
하지만 조직의 AWS 계정, 도메인, 권한 체계 안에서 운영해야 한다면 S3와 CloudFront가 더 자연스러울 수 있다.
특히 백엔드도 Lambda, API Gateway, S3를 사용한다면 프론트엔드 배포도 같은 클라우드 계정 안에 두는 편이 운영 정보가 모인다.
선택 기준은 기술 우열이 아니라 운영 맥락이다.
배포 스크립트에 넣을 것
React SPA 배포 스크립트에는 보통 다음이 들어간다.
npm run build
aws s3 sync build/ s3://static-assets-bucket/build/ --delete
aws cloudfront create-invalidation --distribution-id DISTRIBUTION_ID --paths "/*"하지만 운영 데이터와 같은 bucket을 쓴다면 exclude 옵션이 필요할 수 있다.
aws s3 sync build/ s3://console-bucket/ --delete \
--exclude "data/*" \
--exclude "latest/*"이 옵션이 왜 필요한지 문서에 남겨야 한다. 그렇지 않으면 누군가 정리하다가 제거할 수 있다.
배포 흐름을 그림으로 보면 책임 경계가 더 명확하다.

정적 파일은 프론트엔드 배포가 갱신하고, 데이터 파일은 별도 동기화나 백엔드가 갱신한다. 이 둘을 같은 명령으로 지우거나 덮어쓰지 않게 하는 것이 작은 콘솔 배포에서 꽤 중요하다.
정리
React SPA는 S3와 CloudFront만으로도 충분히 운영할 수 있다.
하지만 단순히 파일을 올리는 일이 아니다.
먼저 정해야 할 것이 있다.
- 정적 파일과 데이터 파일의 위치
- 커스텀 도메인과 HTTPS
- SPA 라우팅 fallback
- 파일별 캐시 정책
- 배포 후 invalidation
- 데이터 파일 보호
정적 호스팅은 서버가 없어서 단순한 것이 아니라, 서버 운영 책임을 CloudFront와 S3로 옮기는 선택이다.
그 책임 경계를 이해하고 배포해야 안정적으로 운영할 수 있다.
함께 볼 GitHub 저장소
'성장과 기술 > 시스템 설계' 카테고리의 다른 글
| 리포트 시스템을 다시 설계한다면 볼 것들 (0) | 2026.06.07 |
|---|---|
| 운영 DB와 리포트 DB를 나눠 생각하기 (0) | 2026.06.06 |
| 월별 통계 데이터를 미리 적재하는 이유 (0) | 2026.06.05 |
| Polling UX는 나쁜 선택일까 (0) | 2026.06.04 |
| Worker가 결과 파일을 만들고 Object Storage에 올리는 흐름 (0) | 2026.06.03 |
글에서 정리한 생각은 GitHub의 코드와 포트폴리오로 이어지고, 일부는 FamBlend 같은 제품 실험으로 확장됩니다.