1. 핵심 개념
CDN(Content Delivery Network)은 전 세계에 분산된 서버에 파일을 미리 복사해두고, 사용자와 가장 가까운 서버에서 응답하는 시스템이다.
비유하자면, 서울에 있는 본사 창고에서 전국에 배송하는 대신 전국 곳곳에 물류 창고를 미리 만들어두는 것과 같다. 부산 사용자는 서울 창고까지 갈 필요 없이 가장 가까운 부산 창고에서 바로 받는다.
핵심 목적은 세 가지다. 사용자와 가까운 서버에서 응답해 속도를 높이고, 원본 서버로 요청이 몰리지 않아 부하를 줄이고, 장애 시 다른 엣지 서버가 대신 응답해 가용성을 높인다.
중요한 포인트 하나 — CDN은 Next.js가 관리하는 게 아니라 배포한 플랫폼(Vercel, AWS 등)이 운영한다.
| 역할 | 주체 |
|---|---|
| 프레임워크 | Next.js |
| 서버 운영 | Vercel, AWS, Netlify |
| CDN 운영 | Cloudflare, Fastly, Akamai 등 |
2. 왜 이렇게 설계하는가 ⭐
CDN이 없으면 한국 사용자든 미국 사용자든 모두 원본 서버(Origin)에 직접 요청해야 한다.
트래픽이 몰릴수록 서버는 과부하되고, 물리적 거리가 멀수록 응답이 느려진다.
CDN은 이 문제를 두 가지 방식으로 해결한다.
첫째, 지리적 분산.
원본 서버가 한국에 있어도, 미국 CDN 엣지 서버에 파일이 복사되어 있으면 미국 사용자는 한국까지 요청이 안 가도 된다.
둘째, 캐싱.
한 번 요청된 파일은 엣지 서버에 저장(Cache Hit)되어, 이후 요청은 원본 서버를 아예 거치지 않는다. 원본 서버 입장에서는 트래픽이 대폭 감소한다.
Next.js의 getStaticProps(SSG)가 CDN과 궁합이 좋은 이유가 바로 여기 있다.
빌드 타임에 만들어진 HTML은 고정된 파일이라 CDN에 그대로 올릴 수 있고, 이후 사용자 요청은 서버 연산 없이 CDN에서 즉시 응답된다.
"SSG가 왜 빠른가요?" 질문에 "빌드 타임에 만들어지기 때문"으로만 답하면 약하다.
"빌드된 HTML이 CDN에 올라가서 원본 서버를 거치지 않고 사용자와 가장 가까운 엣지 서버에서 즉시 응답되기 때문"이라고 답해야 깊이가 보인다.
3. 사용 원리 (코드 실행 흐름)
getStaticProps → CDN 업로드 흐름
// [입력] 빌드 타임 — 요청 없음, 외부 API 호출
export const getStaticProps = async () => {
const data = await fetchData();
// [처리] 서버에서 HTML 파일 생성
return {
props: { data },
revalidate: 60, // 60초마다 백그라운드 재생성 (ISR)
};
// [출력] 생성된 HTML → 배포 플랫폼이 자동으로 CDN에 업로드
// 이후 유저 요청은 원본 서버가 아닌 CDN 엣지 서버에서 응답
};Cache-Control 헤더로 CDN 캐싱 직접 제어
// next.config.js
// [입력] 특정 경로의 응답 헤더 설정
// [처리] CDN이 이 헤더를 읽고 캐싱 여부와 TTL 결정
// [출력] /static/* 경로는 1년간 CDN에 캐싱됨
module.exports = {
async headers() {
return [
{
source: '/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
// public: CDN 캐싱 허용
// max-age=31536000: 1년 TTL
// immutable: 만료 전까지 재검증 요청 안 함
},
],
},
];
},
};4. 데이터 흐름
Cache Hit (캐시 있음)
- 한국 유저가
/posts/1요청 - 한국 CDN 엣지 서버 도달 → 캐시 확인
/posts/1HTML 캐시 존재 → 원본 서버 요청 없이 즉시 응답- 유저는 수십ms 이내 응답 받음
Cache Miss (캐시 없음 — 첫 요청 또는 TTL 만료)
- 한국 유저가
/posts/2요청 - 한국 CDN 엣지 서버 도달 → 캐시 없음 (Cache Miss)
- 원본 서버(Next.js 서버)로 요청 전달
- HTML 생성 후 CDN 엣지 서버에 저장
- 유저에게 응답 → 이후 동일 요청은 Cache Hit
ISR 흐름 (revalidate 설정 시)
- TTL 60초 경과 → 다음 요청 시 CDN이 "stale" 상태로 판단
- 유저에게는 기존 캐시로 즉시 응답 (응답 지연 없음)
- 백그라운드에서 원본 서버에 재생성 요청
- 새 HTML 생성 완료 → CDN 캐시 교체
- 그 다음 요청부터 새 HTML 응답
5. 주요 옵션 (실무 기준)
| 개념 | 설명 | 실무 포인트 |
|---|---|---|
| Cache Hit | CDN에 캐시 있음 | 원본 서버 요청 없이 즉시 응답 |
| Cache Miss | CDN에 캐시 없음 | 원본 서버까지 요청 전달 |
| TTL | 캐시 유지 시간 | 너무 길면 최신 데이터 반영 지연 |
| Purge | 캐시 강제 삭제 | 긴급 수정 배포 시 사용 |
| Edge Server | CDN 거점 서버 | 사용자와 물리적으로 가장 가까운 서버 |
| Origin | 원본 서버 | Next.js 서버 |
Cache-Control | HTTP 헤더로 캐싱 정책 지정 | public, max-age, s-maxage 등 |
s-maxage | CDN 전용 TTL | 브라우저와 CDN 캐시 시간 분리 가능 |
6. 실무 사용 패턴
패턴 1: Vercel 배포 — 자동 CDN 적용
next build
→ SSG 페이지 HTML 생성
→ Vercel이 자동으로 Fastly CDN에 업로드
→ 전 세계 엣지 서버에 복제
→ 별도 설정 없이 글로벌 CDN 완성Vercel에서는 SSG 페이지에 CDN이 자동 적용된다.
별도 설정 없이도
Cache-Control: public, max-age=31536000 헤더가 붙어 엣지 서버에서 즉시 응답된다.패턴 2: AWS 배포 — CloudFront 수동 설정
next build
→ EC2 / Lambda@Edge에 Next.js 배포
→ CloudFront 배포 생성 → Origin을 EC2/Lambda로 설정
→ 캐시 동작(Cache Behavior) 경로별 TTL 직접 설정
→ /static/* : TTL 1년 / /api/* : TTL 0 (캐싱 안 함)AWS는 Vercel보다 설정이 복잡하지만 캐시 정책을 경로별로 세밀하게 조절할 수 있다.
API 응답은 캐싱하면 안 되므로
/api/* 경로는 TTL을 반드시 0으로 설정해야 한다.패턴 3: 긴급 수정 시 캐시 Purge
# Vercel — 재배포 시 자동 캐시 무효화
vercel --prod
# Cloudflare — 특정 URL 캐시 즉시 삭제
curl -X DELETE "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {token}" \
-d '{"files":["https://example.com/posts/1"]}'잘못된 데이터가 CDN에 캐싱된 경우, TTL이 만료될 때까지 기다리지 않고
Purge로 즉시 캐시를 삭제할 수 있다. Vercel은 재배포 시 자동 Purge된다.
7. 자주 하는 실수 / 주의사항
❌ 잘못된 방법: API 응답에 Cache-Control: public 적용
// pages/api/user.ts
export default function handler(req, res) {
res.setHeader('Cache-Control', 'public, max-age=3600'); // ❌
res.json({ userId: req.session.userId });
}✅ 올바른 방법: API 응답은 캐싱하지 않거나 private 설정
res.setHeader('Cache-Control', 'no-store'); // ✅ 또는 private🔎 이유: public으로 설정하면 CDN이 해당 응답을 캐싱한다. 사용자별로 달라야 하는 인증 데이터가 다른 유저에게 그대로 응답될 수 있는 심각한 보안 문제가 생긴다.
❌ 잘못된 방법: TTL을 무조건 길게 설정
✅ 올바른 방법: 콘텐츠 업데이트 빈도에 맞게 TTL 설정
🔎 이유: TTL이 너무 길면 데이터 수정 후에도 CDN에 오래된 캐시가 남아 사용자에게 잘못된 정보가 노출된다. 자주 바뀌는 데이터는 TTL을 짧게 잡거나 ISR + Purge 전략을 같이 써야 한다.
❌ 잘못된 방법: SSR 페이지도 CDN으로 캐싱되길 기대
✅ 올바른 방법: CDN 캐싱은 SSG 페이지 중심으로 설계
🔎 이유: SSR 페이지는 요청마다 서버에서 HTML을 새로 생성하기 때문에 CDN 캐싱 효과가 거의 없다. 사용자별 데이터가 없다면 SSG + ISR로 전환해 CDN 캐싱을 적극 활용하는 게 맞다.
8. 한 줄 요약
CDN은 빌드된 HTML을 전 세계 엣지 서버에 복사해두고 사용자와 가까운 곳에서 응답하는 구조로,
Next.js의 SSG가 빠른 핵심 이유는 "빌드 타임에 만들어졌기 때문"이 아니라
"그 결과물이 CDN에 올라가 원본 서버를 아예 거치지 않기 때문"이다.