0biglife.

[Next.js] CDN이란? (Feat. AWS Amplify, CloudFront)

Frontend/Next.js

· 2025-05-09

[Next.js] CDN이란? (Feat. AWS Amplify, CloudFront)

들어가며

웹을 배포할 때 가장 많이 듣는 개념 중 하나가 바로 CDN(Content Delivery Network)이다. 배포를 해봤다면 한 번쯤 접해봤을 것이 분명하다. "정적 자산은 CDN에 올려야 해요." 또는 "CloudFront로 캐싱해서 속도 높였어요." 그런데 CDN은 정확히 무엇이고, 어떻게 작동하며, Next.js 프로젝트에서는 어떻게 적용되는 걸까?

이 글은 CDN을 완전히 이해하고 싶거나, Next.js 프로젝트를 실제로 운영하며 성능 최적화에 관심 있는 사람을 위한 딥다이브 글이다. 개념과 동작 방식을 살펴본 뒤, 현재 블로그가 어떤 사이클로 운영되고 있는지, 그리고 AWS Amplify에서 CDN이 어떻게 적용되는지에 대해 알아본다.

CDN이란?

CDN(Content Delivery Network)은 컨텐츠 전송 네트워크의 약자로, 전 세계에 분산된 서버(엣지 서버, Edge Location)를 통해 사용자와 가까운 위치에서 콘텐츠를 빠르게 제공하는 네트워크 구조다. 예를 들어, 내 블로그에 접속하려는 사용자가 서울에 있다면, 서울에 위치한 CDN 엣지 서버에서 콘텐츠를 제공받는다. 이렇게 하면 원본 서버까지의 물리적 거리를 줄여서 속도를 높일 수 있다.

필요성과 기본 동작 방식

기본적으로 웹 서버는 특정 리전에 위치한다(예: Seoul 리전, Ohio 리전 등). 그런데 사용자가 전 세계에 흩어져 있다면 어떨까?

  • 한국 서버에서 미국 유저가 정적 파일을 요청하면 → 물리적 거리만큼 레이턴시가 증가한다
  • 모든 요청이 원본 서버(origin)까지 가면 → 서버 부하가 증가하고 속도도 저하된다

이러한 문제점을 CDN은 다음과 같이 다음과 같이 해결한다:

1[ 사용자 요청 ]
23[ CDN 엣지 서버 ]
4    ├─ ✅ 캐시 Hit → 즉시 응답
5    └─ ❌ 캐시 Miss
67      [ 원본 서버로 요청 ]
89      [ 응답 받은 후 캐싱 ]
1011      [ 사용자에게 응답 ]
  1. 사용자가 웹 서버에 접속을 시도한다.
  2. CDN 엣지 서버가 사용자의 요청을 가로채서 처리할 수 있는지 확인한다.
  3. 사용자의 위치에 따라 가장 가까운 엣지 서버가 선택된다.
  4. 엣지 서버에 해당 콘텐츠가 없을 경우, 원본(origin) 서버로 요청을 전달한다.
  5. 원본 서버에서 받은 콘텐츠를 엣지 서버가 캐싱한 뒤 사용자에게 응답한다.
  6. 다음 동일한 요청부터는 캐시된 콘텐츠를 엣지 서버가 직접 응답한다.

이러한 동작 방식 덕분에 우리는 CDN이 서버 부하를 줄여주고 사용자 경험을 개선시켜준다라고 비로소 말할 수 있는 것이다. 아래 핵심 개념을 통해 각 요소별 역할을 정리해보자.

CDN의 핵심 개념

잠깐, 엣지 로케이션(또는 엣지 서버), 오리진 이게 다 뭔가? 간단히 살펴보면 다음과 같다.

엣지 서버(Edge Server) / 엣지 로케이션(Edge Location)\

  • 엣지 서버는 전 세계 여러 거점(엣지 로케이션)에 분산되어 있는 CDN 전용 서버를 의미한다!

  • 사용자의 위치와 가까운 엣지 서버가 자동으로 선택되며, 정적 자산을 캐싱하고 응답하는 중간 역할을 수행한다.

  • 엣지 서버의 역할은 단순한 프록시가 아니라, 캐싱 정책이나 압축, TLS 종료 등 다양한 최적화를 수행한다.

  • 엣지 로케이션(Edge Location)은 이러한 엣지 서버가 실제 물리적으로 존재한느 데이터 센터의 위치를 의미한다. 예를 들면 도쿄, 프랑크푸르트 등 이 이에 해당하는 것.

오리진(Oigin)

  • 말그대로 원본 서버를 의미하며, CDN과 연동되는 실제 컨텐츠 소스이자 서버를 의미한다. 예를 들어 AWS S3, EC2 또는 일반적인 웹 서버를 의미한다.

  • 엣지 서버에서 컨텐츠가 존재하지 않으면, 이 오리진 서버에서 컨텐츠를 직접 받아가 캐싱한다.

캐싱(Cache)

  • 아래서 더 자세히 설명하겠지만, 정적 자산(JS, CSS, 이미지 등)을 엣지 서버에 일시적으로 저장해서 동일 요청이 반복되면 이 캐싱된 자산을 직접 동작시킨다.

이렇게 용어와 CDN 기본 동작을 살펴보았는데, 간단히 정리하면 CDN은 최초에만 원본 서버에 요청을 보내고, 이후에는 엣지 서버에서 캐시된 파일을 기반으로 직접 응답하게 된다. 이로 인해 웹 서버의 부하를 줄이고, 사용자에게 더 빠른 응답을 제공할 수 있는 것!이다.

CDN의 대상

CDN은 모든 요청에 대해 캐싱을 할 수 있는 것은 아니다. CDN은 정적 자산에 대해 캐싱을 한다. 즉, 변하지 않는 파일에 대해서만 캐싱이 가능하다. 예를 들어, 사용자가 요청한 HTML 페이지가 매번 바뀌는 경우에는 CDN에서 캐싱할 수 없다.

따라서, CDN은 일반적으로 아래와 같은 정적 자산에 적합하다.

  • SSG로 미리 렌더된 정적 HTML 페이지
  • 빌드 타임에 생성되는 CSS, JS 번들
  • public의 이미지
  • 아이콘, 웹 폰트, 비디오 파일(비디오 파일의 경우 용량이 클수록 CDN은 필수인 셈)

따라서, 위와 같은 정적 자산이 아닌 SSR로 실시간 렌더되는 페이지나 API 응답은 CDN 캐싱이 당연히 불가능하다.

캐시의 기준은 어떻게 될까?

어떤 기준으로 캐시가 되고, 어디에 저장되고 또 어떻게 업데이트되는지 잠시 CDN 내부의 캐시 동작 과정을 세부적으로 파헤쳐보자.

1. 캐싱의 기본 단위: 컨텐츠 + URL

CDN의 캐싱은 요청 URL 단위로 컨텐츠를 저장한다. 예를 들어 GET https://0biglife.com/images/logo.png 라는 요청이 발생하면, CDN 엣지 서버는 이 URL의 리소스를 캐시 키로 판단한다. 동일한 URL 요청이 또 들어오면, 이 캐시 키(URL)을 기준으로 캐시된 데이터 그대로 응답시키고, 다르다면 다른 캐시로 판단이 된다.

2. 캐시 저장 위치: 엣지 서버의 디스크 또는 메모리

캐시는 전 세계에 분산된 CDN 엣지 서버의 로컬 저장소에 저장된다. 각 엣지 서버는 자신이 응답한 리소스를 저장두고, TTL(Time-To-Live) 또는 LRU(Least Recently Used) 정책에 따라 만료되거나 삭제된다(TTL은 캐시된 컨텐츠가 살아있는 시간, LRU는 캐시 저장 공간이 꽉 찼을 때 사용하는 캐시 정리 전략이다. 일반적으로 우리가 아는 Cache-Control: max-age=86400이 TTL에 해당된다). 캐시가 만료되었거나 없을 시 위에서 설명하였듯 원본 서버(origin)에 요청하여 다시 저장한다.

3. 캐시 만료 기준: Cache-Control, Expires

CDN이 컨텐츠를 얼마나 오래 캐싱할지는 주로 HTTP 헤더로 결정된다.

1Cache-Control: public, max-age=86400
  • public: CDN이 이걸 저장해도 된다는 의미

  • max-age: 86400초, 즉 24시간 동안 캐시하겠다는 의미

1Expires: Wed, 21 Oct 2025 07:28:00 GMT
  • Expires: '이 날짜까지 무조건 캐시를 유지해라'를 의미

현재 블로그 구조

현재 이 기술 블로그는 Next.js + AWS Amplify 조합으로 운영되고 있다. 눈여겨볼 점은, 이 블로그가 아무런 CDN 설정을 직접 하지 않았음에도 CloudFront 기반 CDN이 자동으로 구성되어있단느 점이다.

AWS Amplify에 프로젝트를 배포하게 되면, AWS는 내부적으로 CloudFront라는 CDN 서비스를 통해 글로벌 CDN 인프라를 자동 세팅해준다(엄청 간편하지 않은가.!). 따라서 CloudFront 콘솔에 들어가서 배포를 수동으로 설정할 필요가 전혀 없고 Amplify가 알아서 엣지 서버를 활용한 정적 파일 배포를 처리해준다.

그렇기 때문에, Next.js와 AWS를 사용한다면 이 구조가 블로그 운영하기엔 가장 최적화된 구조가 아닐까 싶다. 구체적으로 살펴보면 다음과 같다.

1. Next.js의 SSG 지원

이전 게시글에서도 다뤘지만, Next.js는 output: "export" 또는 output: "standalone" 설정을 통해 정적 사이트 생성이 가능하다. export는 완전한 정적 HTML 파일만 생성하고, standalone 은 서버 기능(Node.js 런타임 포함)을 갖춘 독립 실행형 앱을 생성한다. SSG는 빌드 시점에 HTML을 미리 만들어두고, 이를 CDN에 올려두는 방식이기에 상자 요청 시 렌더링 비용 없이 빠르게 응답이 가능하다.

1# Next.js 정적 자산 위치 : outeput: "export" 기준
2- `.next/static/` : JavaScript, CSS, 이미지
3- `out/` : export된 HTML (SSG)
4- `public/` : 사용자 업로드 파일, favicon 등

즉, 이 블로그는 게시글 페이지(/post/[slug])를 포함한 모든 페이지가 미리 렌더링된 정적 HTML 파일로 존재하며, JS, CSS, 이미지, gif 등 모두 정적 자산이기 때문에 CDN에 적합하다.

2. Amplify 프로젝트 배포

AWS Amplify는 Github 연동 코드를 빌드한 뒤, 자동으로 정적 자산을 S3에 업로드하고, 이를 CloudFront를 통해 전 세계에 배포한다. 배포가 완료되면, Amplify가 알아서 S3 + CloudFront 조합을 구성해준다.

단, 여기서 S3 콘솔에 동일 리전으로 접근해서 조회해보았으나 해당 버킷은 보이지 않는다. 이는 AWS가 Amplify Hosting에서 사용하는 S3 버킷을 내부적으로 관리하여 사요자에게 직접적인 접근 권한을 부여하지 않기 때문이다.

3. Amplify 기반 CloudFront 자동 구성

AWS의 대표적인 글로벌 CDN 서비스인 CloudFront는 엣지 로케이션을 통해 전 세계 수백 개의 거점에서 컨텐츠를 캐싱하고 제공한다. 그렇기 때문에 아래와 같은 프로세스가 가능해지고, 이러한 편리한 서비스 덕에 개발자가 직접 캐시 정책을 설정하지 않아도, 기본적으로 정적 자산(JS, CSS, 이미지, HTML 등)은 적절한 TTL을 바탕으로 전 세계 엣지 서버에 자동으로 캐싱된다.

  1. 사용자가 https://0biglife.com/를 요청
  2. CloudFront가 해당 요청을 엣지 서버에서 먼저 찾음
  3. 캐시된 콘텐츠가 있다면 → 엣지 서버에서 즉시 응답
  4. 없다면 → 원본 서버로 요청
  5. 가져온 콘텐츠를 엣지 서버에 캐시한 후 사용자에게 응답
  6. 이후 동일 요청은 캐시에서 직접 응답

잠깐, 캐싱 정책은 어떻게 조회하지?

음.. 여기서 문제점은 Amplify로 배포된 CloudFront는 CloudFront 콘솔에서 조회가 불가능하다는 것이다. Amplify가 자동 생성한 CloudFront는 Amplify 소유의 리소스이기 때문에 일반 CloudFront 콘솔에 노출되지 않는다. 따라서 우리는 AWS 콘솔이 아닌 curl 명령어로 조회해야한다.

1➜  ~ curl -I 0biglife.com
2
3HTTP/1.1 301 Moved Permanently
4Server: CloudFront
5Content-Type: text/html
6Location: https://0biglife.com/
7X-Cache: Redirect from cloudfront
8Via: 1.1 8e9...93.cloudfront.net (CloudFront)
9X-Amz-Cf-Pop: ICN57-P6
10Alt-Svc: h3=":443"; ma=86400
11X-Amz-Cf-Id: uEcKl...==

curl -I <url>로 확인해보면 위와 같은 정보가 반환된다. 여기서 CloudFront를 통해 응답이 오는지, 엣지 서버를 경유하는지 확인이 가능하다.

  • Server: CloudFront : CloudFront 엣지 서버에서 응답이 왔다
  • X-Cache: Redirect from CloudFront: CloudFront에서 리다이렉트가 발생했다
  • Via: 1.1 8e9...93.cloudfront.net (CloudFront): CloudFront를 경유했다
  • X-Amz-Cf-Pop: ICN57-P6: 서울 리전의 엣지 로케이션을

캐싱 정책 수정하기

수동으로 만든 CloudFront이라면 동작 카테고리에서 캐시 정책에 대한 수정 작업도 간편하게 할 수 있다. 그러나 다시 한 번 말하지만 Amplify 기반 CloudFront는 직접 캐시 정책을 수정할 수 없다. next.config.js에서 설정하는 headers() 는 Next.js 서버가 요청을 직접 처리할 때만 적용되기에 정적 파일 호스팅되는 현재 우리에게는 전혀 쓸만한 선택지가 아니다.

그렇다면, Amplify 기반 CloudFront의 캐시 정책을 수정할 수 있는 방법은 없을까? 있다. Amplify 콘솔의 사용자 지정 헤더 설정에서 customHTTP.yml을 통해 할 수 있다. 먼저 프로젝트 루트에 다음 yaml 파일을 만들어주자.

1headers:
2  - source: "/_next/static/*"
3    headers:
4      - key: "Cache-Control"
5        value: "public, max-age=31536000, immutable"
6
7  - source: "/favicon.ico"
8    headers:
9      - key: "Cache-Control"
10        value: "public, max-age=31536000"
11
12  - source: "/"
13    headers:
14      - key: "Cache-Control"
15        value: "public, max-age=3600, stale-while-revalidate=86400"
16
17  - source: "/posts/*"
18    headers:
19      - key: "Cache-Control"
20        value: "public, max-age=3600, stale-while-revalidate=86400"
21  # 추가 설정

이제 git push로 빌드 배포를 진행한 뒤 curl -I를 돌리되 yarn build로 생성된 .next/static/chunks/{main-file-name}.js을 명령어 뒤에 입력해주자. 그리고 여기서 주의할 점이 하나 더 있다. Amplify 빌드 시 사용되는 amplify.yamlbaseDirectory 역시 프로젝트 경로에 맞게 설정해줘야한다.

1➜  ~ curl -I https://www.0biglife.com/_next/static/chunks/{main-file-name}.js
2
3HTTP/2 200
4content-type: text/javascript
5accept-ranges: bytes
6etag: "fe52...a9be"
7last-modified: Wed, 14 May 2025 08:23:50 GMT
8cache-control: public, max-age=31536000, immutable # <-- 설정 확인
9vary: Accept-Encoding
10x-cache: Hit from cloudfront # <-- Miss from 이 나오면 안됨!
11via: 1.1 a5c1e8...234.cloudfront.net (CloudFront)
12x-amz-cf-pop: ICN57-P6
13alt-svc: h3=":443"; ma=86400
14x-amz-cf-id: 8KPB-FH4...r1Q==

이렇게 반환이 되면 가장 먼저 보아야할 것이 x-cacheHit from cloudfront이다. 이 값이 Hit from 이라면 캐시된 자산을 엣지 서버에서 직접 응답한 것이고, Miss from 이라면 원본 서버에서 직접 응답한 것이다. 즉, 캐시가 되지 않은 것이다. 그리고 cache-control 헤더를 통해 우리가 설정한 캐시 정책이 제대로 반영되었는지 확인할 수 있다.

마치며

여기까지 CDN이 뭔지, 어떻게 동작하는지와 현재 블로그의 SSG 방식, AWS를 통한 CDN 설정이 어떻게 되어있는지 살펴보았다. 원래 취지는 CDN이 뭔지에 대해서만 간략하게 다루려고 했는데, 혹여 정적 페이지를 Amplify로 관리하는 사람들이 있으면 도움이 되곘다 싶어서 블로그 구조까지 붙여서 정리해보았다. CDN은 단순한 정적 파일 네트워크라 아니라, 글로벌 트래픽을 안정적으로 감당하고 성능과 비용을 최적화하는 인프라 요소다. Next.js와 AWS Amplify, Cloudfront와 함께 만나면 복잡한 CDN 구성과 운영을 개발자는 거의 손대지 않아도 자동으로 완성된다. 이 내용이 혹자에게 CDN 이해와 실전 적용에 도움이 되길 바란다.

Index

들어가며CDN이란?필요성과 기본 동작 방식CDN의 핵심 개념CDN의 대상캐시의 기준은 어떻게 될까?현재 블로그 구조1. Next.js의 SSG 지원2. Amplify 프로젝트 배포3. Amplify 기반 CloudFront 자동 구성잠깐, 캐싱 정책은 어떻게 조회하지?캐싱 정책 수정하기마치며