frontend/javascript

🟨 2-33. 대규모 트래픽에도 안 버벅이는 프론트엔드 — CDN, Lazy Loading, Prefetching 실전 전략

mirabo01 2025. 11. 7. 08:59

1. 왜 트래픽이 몰리면 갑자기 느려질까?

트래픽이 몰린다고 해서 단순히 “사용자가 많아서” 느려지는 건 아니야.
대부분은 이런 것들이 한꺼번에 터지기 때문이지.

  • 오리진 서버(origin)로 가는 요청이 너무 많다
  • 정적 파일(JS/CSS/이미지)이 전부 같은 서버에서 나간다
  • 같은 데이터를 매번 새로 fetch 한다
  • 안 봐도 되는 컴포넌트/이미지를 다 한 번에 로드한다
  • “다음에 쓸 리소스”를 전혀 미리 가져오지 않는다

즉, 핵심은 두 가지다.

  1. 오리진(백엔드/메인 서버)에 가는 트래픽을 줄이는 것
  2. 사용자 입장에서 “필요한 순간에만 필요한 것만” 로딩하는 것

이 두 가지를 중심으로 CDN, Lazy Loading, Prefetching 전략을 묶어볼 거야.


2. CDN 전략 — 정적 리소스는 최대한 “가까운 데”서 쏜다

2-1. CDN의 역할

CDN(Content Delivery Network)은 전 세계에 흩어진 서버(POP)들이
정적 리소스(JS, CSS, 이미지, 폰트) 를 대신 제공해 주는 네트워크야.

효과는 딱 두 가지:

  • 사용자는 지리적으로 가까운 서버에서 파일을 받는다 → 네트워크 지연 감소
  • 오리진 서버는 정적 파일 제공 부담이 줄어든다 → 백엔드 여유 확보

대규모 트래픽에서는 “정적 리소스를 CDN으로 무조건 빼는 것”이 전제 조건이야.

2-2. 어떤 걸 CDN에 올려야 할까?

  • JS 번들 파일 (.js)
  • CSS (.css)
  • 이미지 (.jpg, .png, .webp, .avif)
  • 폰트 (.woff2, .ttf)
  • 동영상 썸네일, 아이콘, 정적 JSON 등

그리고 가능하면 정적 도메인 분리도 해 주는 게 좋아.

  • www.example.com → HTML/데이터
  • static.example.com 또는 CDN 도메인 → JS/CSS/이미지

2-3. 캐시 정책 설계

CDN을 쓴다고 끝이 아니라, “어떻게 캐시할지” 가 진짜 중요하다.

  • 파일 이름에 해시 붙이기: app.3f92a.js, style.8cd1.css
  • 헤더에 강한 캐시 설정
    • Cache-Control: public, max-age=31536000, immutable

이렇게 하면:

  • 파일 내용이 안 바뀌면 CDN과 브라우저 캐시가 계속 사용
  • 새로 빌드하면 파일명이 바뀌니 자동으로 최신 버전으로 교체

즉, “캐시는 세게, 파일명으로 버전 관리”가 기본 패턴이다.


3. Lazy Loading — 안 보는 건 나중에 불러라

대규모 트래픽 + 대규모 UI에서 중요한 철학은 이거야.

“지금 안 쓰는 건 지금 로드하지 않는다.”

3-1. 이미지 Lazy Loading

가장 쉬운 부분부터.

<img src="thumb.jpg" loading="lazy" alt="썸네일" />
  • loading="lazy" 속성 하나로,
  • 뷰포트 근처에 왔을 때만 이미지가 로드됨
  • “긴 리스트 + 썸네일 많은 페이지”면 효과가 매우 크다

IntersectionObserver로 커스텀 구현도 가능해.

3-2. 컴포넌트/페이지 Lazy Loading (코드 스플리팅)

SPA/MPA 상관 없이, 한 번에 모든 코드를 불러오면 첫 로딩이 괴물이 된다.

  • 라우트 단위 코드 스플리팅
  • 컴포넌트 단위 Lazy Loading

예를 들어 (React 기준 느낌으로 설명하자면):

const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

function Page() {
  return (
    <Suspense fallback={<div>로딩 중...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}
  • 유저가 해당 컴포넌트를 보기 전까지는 JS를 로드하지 않음
  • JS 번들 크기 감소 → 초기 로딩 및 파싱 시간 감소

3-3. “스크롤 기반” Lazy UI

긴 목록(피드, 게시글 리스트, 상품 목록)에서

  • 처음부터 100개 렌더링 ❌
  • 위에서 10~20개만 렌더링, 아래는 Infinite Scroll + Lazy Load

이 구조가 되면

  • DOM 노드 수가 줄어들어 렌더링 비용 감소
  • 데이터 API 호출도 페이지네이션 단위로 분산

대규모 트래픽에선, 이런 “조각 나누기”가 오리진 트래픽과 렌더링 부담을 동시에 줄인다.


4. Prefetch / Preload — “미리 받아두기”의 힘

Lazy가 “나중에”라면, Prefetch/Preload는 “미리” 다.
대규모 트래픽에서도 체감 속도를 올리려면 이걸 잘 써야 한다.

4-1. Preload

지금 페이지에서 곧 사용할 리소스를 빠르게 받아오고 싶을 때.

<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/static/hero.jpg" as="image">
<link rel="preload" href="/static/main.js" as="script">
  • LCP에 영향을 주는 Hero 이미지, 주요 폰트, 상단 영역 JS/CSS를 preload
  • “화면이 뜨긴 떴는데 폰트/이미지가 늦게 나오는” 문제를 줄여준다

4-2. Prefetch

지금은 안 쓰지만, 곧 쓰게 될 리소스.
예: 메인에서 로그인 페이지, 상세 페이지로 많이 이동한다면 👇

<link rel="prefetch" href="/page/detail" as="document">

또는 라우팅 라이브러리들이 제공하는

  • “링크에 마우스 올리면 해당 페이지 리소스 prefetch”
  • “뷰포트 근처에 링크가 오면, 미리 다음 페이지 번들 로드”

이런 기능을 켜두면, 사용자 입장에선 페이지 전환이 거의 즉시 일어나는 느낌을 받게 된다.

4-3. Preconnect / DNS Prefetch

외부 도메인(예: 이미지 CDN, API 서버, 폰트 서버)에 대해


  • DNS 조회, TLS 핸드셰이크를 미리 해두는 것
  • 트래픽이 많을수록 “연결 지연”이 체감 속도에 크게 영향을 준다

5. 캐싱 레이어 설계 — 오리진을 최대한 “심심하게”

대규모 트래픽에서 중요한 아이디어는 하나야.

“오리진 서버는 심심할수록 좋다.”

그걸 위해 캐싱 레이어를 여러 단계로 쌓는다.

5-1. 브라우저 캐시

  • Cache-Control, ETag, Last-Modified
  • 정적 파일은 강하게, HTML/API는 전략적으로

5-2. CDN 캐시

  • 전 세계 POP에서 동일 파일 제공
  • 인기 있는 페이지일수록 “오리진 요청 거의 없음” 상태까지 만들 수도 있다

5-3. Service Worker / PWA 캐시

  • 자주 쓰는 API 응답, 정적 JSON, 번역 리소스 등을 SW 캐시로 재사용
  • 오프라인/저속 네트워크 환경에서도 UX 유지

5-4. 프론트엔드 in-memory 캐시 (React Query / SWR 등)

  • 동일한 API 요청을 한 페이지에서 여러 번 보내지 않도록
  • stale-while-revalidate 패턴으로 바로 캐시 → 백그라운드에서 최신 데이터 받아오기

이렇게 하면:

  • 프론트단에서 같은 요청이 1번만 서버로 가고, 나머지는 캐시로 해결
  • 동시 접속 수가 많을수록 오리진 서버 보호에 엄청난 효과

6. 이미지 & 리소스 최적화 — 트래픽 절반은 이미지다

대규모 트래픽 시 트래픽량 = 돈이다. (CDN 비용, 네트워크 비용)

그래서 이미지/정적 리소스를 줄이는 건 성능 + 비용 모두에 중요해.

6-1. 이미지 포맷

  • 사진: WebP, AVIF 우선
  • 아이콘/로고: SVG

6-2. Responsive Image (srcset, sizes)

<img
  src="image-800.jpg"
  srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
  sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
  alt="예시 이미지"
/>
  • 모바일에서 굳이 데스크톱 해상도 이미지를 받을 필요가 없지
  • 작은 화면에는 작은 이미지 → 네트워크 절약 → LCP 개선

6-3. 아이콘 스프라이트 / SVG Sprite

  • 수백 개 아이콘을 개별 요청 ❌
  • SVG Sprite 한 번 로드 → <use>로 재사용

7. 렌더링 전략 — SSR / SSG / Edge / ISR

대규모 트래픽 환경에서는 “렌더링 방식 선택”도 중요하다.

  • SSR (Server-Side Rendering)
    • 매 요청마다 HTML 생성 → 동적이 많으면 좋지만, 트래픽 많으면 서버 부담 큼
  • SSG (Static Site Generation)
    • 빌드 시 HTML 생성 → CDN 캐시 + 정적 제공 → 트래픽 많을수록 유리
  • ISR (Incremental Static Regeneration)
    • 자주 안 바뀌는 페이지는 정적으로, 일정 주기마다 백그라운드 갱신
  • Edge Rendering
    • 사용자 근처 엣지 서버에서 SSR 수행 → TTFB 개선

패턴은 보통 이렇다 👇

  • 상품 상세, 게시글 페이지: SSG + ISR
  • 마이페이지, 관리자: SSR or CSR
  • 홈/랭킹/카테고리: SSG + CDN 캐시 극대화

8. 예시 시나리오 — 동접 10만 메인 페이지 설계

가정:

  • 메인 페이지 동시 접속 10만
  • 상품 카드 20개, 이미지, 썸네일, 배너 포함

어떻게 설계할까?

  1. HTML은 SSG 또는 ISR로 생성 → CDN 캐시
  2. JS/CSS/이미지는 전부 CDN 도메인으로 제공
  3. Hero 이미지, 주요 폰트 preload
  4. 상품 카드 이미지는 loading="lazy" + srcset 사용
  5. 스크롤 아래 컨텐츠(추천, 더보기 영역)는 Lazy Load 컴포넌트로 분리
  6. API 데이터는 React Query(or SWR)로 캐싱, stale-while-revalidate 적용
  7. “상세 페이지” 링크는 prefetch 해서 클릭 시 거의 즉시 전환
  8. Core Web Vitals(LCP/FID/CLS)를 Web Vitals + Sentry로 모니터링

이렇게 해 두면:

  • 트래픽이 늘어도 오리진이 터지지 않고
  • 사용자 입장에서는 “첫 화면은 빠르고, 나머지는 자연스럽게 따라오는” 느낌을 받게 된다.

9. 체크리스트 — 대규모 트래픽 대비 빠른 점검표

CDN

  • JS/CSS/이미지/폰트 전부 CDN에서 제공되는가
  • 정적 파일 이름에 해시가 붙어 있는가
  • Cache-Control: max-age + immutable 사용 중인가

Lazy Loading

  • 이미지에 loading="lazy" 또는 IntersectionObserver 적용했는가
  • 라우트/컴포넌트 코드 스플리팅이 되어 있는가
  • 긴 리스트를 한 번에 다 렌더링하고 있지는 않은가

Prefetch / Preload

  • Hero 이미지, 폰트, 핵심 JS/CSS에 preload를 써봤는가
  • 다음 페이지/주요 라우트에 prefetch를 적용했는가
  • 외부 도메인에 preconnect/dns-prefetch를 활용했는가

캐싱 레이어

  • 브라우저 캐시 + CDN 캐시 + 프론트단 in-memory 캐시 구조가 있는가
  • 자주 불리는 API는 SWR/React Query처럼 캐시를 쓰고 있는가

렌더링 전략

  • 정말 SSR이 필요한 페이지만 SSR 하고 있는가
  • 자주 보는 정적 페이지는 SSG/ISR로 돌리고 있는가

10. 마무리

대규모 트래픽 환경에서의 프론트엔드 최적화는
“한 가지 기술로 해결되는 문제”가 아니라,

  • 어디에서 데이터를 가져오고(CDN, 캐시),
  • 언제 로딩하며(Lazy, Prefetch),
  • 무엇을 먼저 보여줄지(LCP 관점),
  • 백엔드와 어떻게 부담을 나눌지(렌더링 전략)

전체 흐름을 설계하는 작업에 가깝다.

트래픽이 많아질수록,
“한 번만 요청하고, 여러 번 재사용하는 구조”가 정답에 가까워진다.

이 감각만 가져가면, 어떤 스택(React, Next.js, Vue, Svelte…)을 쓰든
대규모 트래픽에도 흔들리지 않는 프론트엔드를 설계할 수 있다.