웹폰트, CDN vs 직접 호스팅 어떤 게 좋을까? 💭

별이·2025년 3월 1일

웹 개발하면서 폰트는 항상 CDN 방식으로 가져와서 사용했더니 다른 방식에 대해서는 거의 잊고 있었다가 최근에 듣던 강의에서 폰트 파일을 직접 public 폴더에 넣고 불러오는 방식을 사용하는 것을 보고 '아! 이 방법도 있었지!' 생각이 들면서 그럼 어떤 방식이 더 좋은 걸까? CDN에서 가져오는 게 좋을까, 아니면 직접 파일로 가져오는 게 좋을까? 의문점이 생겼다. 이 의문을 해결하기 위해 찾아보다가 결국 웹폰트 최적화에 대해 공부해야겠다는 생각이 들었다. 이번 기회에 웹폰트 최적화에 대해 제대로 정리해 보려 한다. 👩🏻‍💻💭

🔎 폰트 최적화에 대해 알아보기 전에, 먼저 웹에서 사용할 수 있는 폰트 종류에 대해 정리해 봤다.

💡 시스템 폰트(System Font)

  • 사용자 운영체제(Windows, macOS, Linux 등)에 미리 설치된 기본 폰트
  • 별도 다운로드 필요 없이 바로 사용 가능
  • 개발자가 별도의 폰트를 지정하지 않으면 시스템 폰트가 기본적으로 사용됨

💡 로컬 폰트(Local Font)

  • 서버에 폰트 파일을 업로드하고 CSS로 적용하는 방식
  • 웹 서버에서 직접 제공하기 때문에 웹 폰트보다 로딩 속도가 상대적으로 빠름
  • 빠른 초기 로딩 속도: 외부 서버 연결 없이 바로 불러옴
  • 네트워크 부하 감소: 외부 서버에 의존하지 않음
  • 필요한 폰트만 포함 가능: 리소스 효율적 관리 가능

💡 웹 폰트(Web Font)

  • 온라인의 특정 서버(ex: 구글 폰트)에 위ㅣ한 폰트 파일을 다운로드하여 사용
  • 캐싱과 브라우저 최적화: 여러 웹사이트에서 동일한 폰트 사용 시 중복 다운로드 방지
  • 폰트 버전 관리: 서비스 제공자가 폰트 업데이트 및 관리
  • 모바일 최적화: 모바일 환경에서도 최적화 가능

💭 웹폰트가 성능에 미치는 영향

웹 폰트는 사이트의 디자인과 브랜딩에 중요하지만, 폰트 파일은 생각보다 크기 때문에 성능에 영향을 미친다.

1. 추가적인 HTTP 요청

각 폰트 파일마다 별도의 네트워크 요청이 필요하다. 폰트 패밀리 하나에도 보통 레귤러, 볼드, 이탤릭 등 여러 스타일이 있고, 각각 별도의 HTTP 요청을 발생시킨다. HTTP/1.1 환경에서는 동시 연결 수 제한으로 인해 이런 요청이 병목 현상을 일으킬 수 있다.

2. 다운로드 시간

특히 무거운 폰트 파일이나 여러 폰트 웨이트를 사용할 경우 다운로드 시간이 길어진다. 한글 폰트는 영어 폰트에 비해 파일 크기가 훨씬 크다. 일반적인 영문 폰트가 20~30KB 정도인 반면, 한글 폰트는 기본만 해도 1~2MB에 달하는 경우가 많다. 이는 특히 모바일 환경이나 느린 네트워크에서 큰 문제가 될 수 있다.

3. 렌더링 지연

폰트가 로드될 때까지 텍스트 표시가 지연되거나 스타일 변경될 수 있다. 이런 현상은 크게 두 가지로 나타난다.

🌟 FOIT(Flash of Invisible Text) : Chrome, Safari 등의 브라우저에서 웹폰트 로딩 중에 텍스트가 보이지 않는 현상. 사용자는 글자 위치에 아무것도 보이지 않다가 폰트가 로드되면 갑자기 텍스트가 나타나는 것을 경험한다.

🌟 FOUT(Flash of Unstyled Text) : IE, Edge 등에서 웹폰트 로딩 중에 기본 폰트로 먼저 보이다가 로딩 완료 후 바뀌는 현상. 텍스트는 보이지만 스타일이 갑자기 바뀌면서 레이아웃이 흔들릴 수 있다.

4. 누적 레이아웃 시프트(CLS)

폰트가 로드되면서 레이아웃이 변경되어 사용자 경험을 해칠 수 있다.
CLS(Cumulative Layout Shift)는 페이지 로딩 중에 레이아웃이 얼마나 많이 바뀌는지 측정하는 지표다. 웹폰트가 로드되면서 글자 크기나 간격이 바뀌면 버튼이나 이미지 등 다른 요소들의 위치도 함께 이동해, 사용자가 클릭하려던 요소가 갑자기 다른 위치로 이동하는 경험을 할 수 있다. 이는 구글의 Core Web Vitals에서 중요하게 측정하는 성능 지표 중 하나다.

💭 웹폰트 최적화 전략

1. 서브셋 폰트 사용하기

한글 폰트는 수천 개의 완성형 글자를 포함하고 있어 파일 크기가 매우 크다. 서브셋 폰트는 실제로 사용하는 글자만 추출해서 폰트 파일을 작게 만드는 방법이다.

<!-- Google Fonts에서는 subset 파라미터로 간단하게 적용 가능 -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap&subset=korean" rel="stylesheet">

직접 서브셋 만들려면 unicode-range를 설정하거나 전문 도구를 사용할 수 있다.

@font-face {
  font-family: 'Open Sans';
  src: local('Open Sans Regular'), local('OpenSans-Regular'),
       url('/fonts/open-sans-subset.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  unicode-range: U+AC00-D7AF; /* 한글 범위 설정 */
}

💡 일반적인 유니코드 범위

  • 일반적인 특수문자(마침표, 쉼표 등): U+0020-002F, U+003A-0040, U+005B-0060, U+007B-007E
  • 모든 기본 다국어 평면(영어 포함): U+0000-FFFF
  • 한글 범위: U+AC00-D7AF

🔗 서브셋 웹폰트 변환 사이트

2. 최신 폰트 포맷 활용하기

폰트 파일 포맷에 따라 압축률과 로딩 속도가 달라진다. 최신 포맷을 우선적으로 사용하면 파일 크기를 크게 줄일 수 있다.

@font-face {
  font-family: 'MyWebFont';
  src: url('myfont.woff2') format('woff2'), /* 최신 브라우저 */
       url('myfont.woff') format('woff'), /* 대부분의 브라우저 */
       url('myfont.ttf') format('truetype'); /* 구형 브라우저 */
}

💡 주요 폰트 포맷 정리

  • WOFF2: 최신 포맷으로 압축률이 가장 높음 (원본보다 30~50% 작은 파일 크기)
  • WOFF: 대부분의 모던 브라우저 지원
  • TTF/OTF: 오래된 포맷으로 파일 크기가 큼

브라우저는 선언된 순서대로 지원하는 첫 번째 포맷을 사용하므로, 가장 효율적인 포맷을 먼저 작성하는 것이 좋다.

3. font-display 속성 활용하기

font-display 속성은 폰트가 로드되는 동안 텍스트가 어떻게 표시될지 제어한다. 이를 통해 FOIT나 FOUT 현상을 관리할 수 있다.

@font-face {
  font-family: 'MyWebFont';
  src: url('webfont.woff2') format('woff2');
  font-display: swap; /* 이 부분이 중요 */
}
  • auto : 브라우저 기본값 (대부분 FOIT)
  • swap : 폰트가 로딩 중에는 기본 폰트로 즉시 표시, 로드 완료 후 웹폰트로 교체 (FOUT 발생)
  • block : 짧은 시간(보통 3초) 동안 텍스트를 표시하지 않다가 폰트가 로드되면 표시 (FOIT와 유사)
  • fallback : 짧은 차단 기간 후 기본 폰트로 표시, 폰트가 빨리 로드되면 교체
  • optional : 브라우저가 네트워크 상태에 따라 폰트 로드 여부 결정 (느린 연결에서는 로드 안 함)

대부분의 경우 swap이 사용자 경험 측면에서 가장 좋은 선택이다. 사용자가 콘텐츠를 즉시 볼 수 있기 때문이다.

4. 폰트 프리로딩 (rel 속성 활용)

중요한 폰트는 미리 로드하도록 지시할 수 있다. 이렇게 하면 브라우저가 다른 리소스보다 폰트를 우선적으로 가져온다.

<link rel="preload" href="webfont.woff2" as="font" type="font/woff2" crossorigin>

💡 rel 속성의 종류

  • preload: 현재 페이지에서 곧 필요한 리소스를 미리 로드
  • prefetch: 다음 페이지에서 필요할 리소스를 미리 로드
  • preconnect: 외부 도메인과의 연결을 미리 설정 (CDN 사용 시 유용)

⚠️ 주의할 점은 너무 많은 리소스를 preload하면 오히려 다른 중요한 리소스의 로딩을 방해할 수 있으므로, 꼭 필요한 폰트만 preload 하는 것이 좋다.

5. 변수 폰트(Variable Fonts) 사용하기

여러 폰트 웨이트나 스타일이 필요할 때 각각 다른 파일을 로드하는 대신, 하나의 변수 폰트로 모든 두께를 커버할 수 있다.

@font-face {
  font-family: 'MyVariableFont';
  src: url('myvariablefont.woff2') format('woff2-variations');
  font-weight: 100 900;
}

.light-text {
  font-weight: 300;
}

.bold-text {
  font-weight: 700;
}

변수 폰트는 여러 스타일을 하나의 파일에 통합하므로, 여러 폰트 웨이트나 스타일을 사용하는 사이트에서 특히 효과적이다. 다만 아직 모든 폰트가 변수 폰트로 제공되지는 않는다.

6. 로컬 폰트 우선 사용하기

사용자의 컴퓨터에 이미 설치된 폰트가 있다면 다운로드 없이 그걸 먼저 사용하도록 설정할 수 있다.

@font-face {
  font-family: 'MyWebFont';
  src: local('Noto Sans KR'),
       url('webfont.woff2') format('woff2');
}

이렇게 하면 'Noto Sans KR'이 사용자 컴퓨터에 이미 설치되어 있을 경우 그걸 사용하고, 없으면 웹폰트를 다운로드한다. 시스템에 설치된 폰트를 활용하므로 추가 다운로드 없이 즉시 텍스트를 표시할 수 있다.

7. 폰트 로딩 방식 선택하기

<!-- link 방식 (권장) -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">

💡 CSS @import 규칙 사용

/* @import 방식 */
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap');

💡 CSS @font-face 직접 정의 (로컬 폰트 사용 시)

@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
}

<link> : 병렬 로딩으로 속도 빠름, 모든 브라우저 지원
@import : CSS 파일이 모두 다운로드된 후에야 로딩 시작, 직렬 방식으로 상대적으로 느림. IE11 이하 지원 안 됨
@font-face : 직접 폰트 파일 지정 시 사용, 자체적으로 성능 차이 없음

8. 폰트 로딩 API 활용하기

CSS의 @font-face 규칙 외에도, JavaScript의 Font Loading API를 사용하면 폰트 로딩을 더 세밀하게 제어할 수 있다. 특히 중요한 텍스트가 빨리 보여야 하거나 폰트 로딩 상태에 따라 UI를 변경하고 싶을 때 유용하다.

// 모든 폰트가 로드됐는지 확인
document.fonts.ready.then(function() {
  console.log('모든 폰트가 로드됐다!');
  document.documentElement.classList.add('fonts-loaded');
});

// 특정 폰트 로드하기
const font = new FontFace('MyWebFont', 'url(webfont.woff2)', {
  style: 'normal',
  weight: '400',
  display: 'swap'
});

font.load().then(function(loadedFont) {
  document.fonts.add(loadedFont);
  document.body.style.fontFamily = 'MyWebFont, sans-serif';
});

이 방식을 사용하면
1. 조건부 폰트 로딩: 필요한 경우에만 특정 폰트를 로드
2. 폰트 로딩 상태 추적: 폰트 로드 시점에 UI 업데이트
3. 폰트 로딩 실패 처리: 네트워크 오류 시 대체 방안 제공

아직 직접 사용해 본 적은 없지만, 복잡한 폰트 전략이 필요하거나 사용자 경험을 최대한 세밀하게 제어하고 싶을 때 유용할 것 같다.

🤔 그렇다면 내 프로젝트에 어떤 방식이 더 좋을까..?

내가 고민했던 것은 이런 상황이었다.

CDN 방식 (외부 주소 참조)

@font-face {
  font-family: 'Ownglyph';
  src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/2411-3@1.0/Ownglyph_ParkDaHyun.woff2") format('woff2');
}

직접 호스팅 방식 (로컬 폰트)

@font-face {
  font-family: 'Ownglyph';
  src: url("/Ownglyph_ParkDaHyun.ttf");
}

첫 번째는 눈누에서 제공하는 CDN 주소를 직접 참조하는 방식이고, 두 번째는 폰트 파일을 다운로드 받아 내 프로젝트의 public 폴더에 넣고 불러오는 방식이다.

🔎 CDN 방식 테스트 결과 (프로덕션 환경)

  • 첫 로딩 시간: 0ms
  • 이미 WOFF2 포맷으로 최적화되어 있음
  • 파일 크기가 메모리에 저장되어 있어 추가 리소스 사용 없음

🔎 직접 호스팅 테스트 결과 (프로덕션 환경)

  • 첫 로딩 시 304 상태 코드(Not Modified)와 함께 약 7ms 로딩 시간 기록
  • 이후 로딩에서는 200 상태 코드와 함께 0ms 로딩 시간 (메모리에서 로드)
  • 파일 크기는 179B로 표시됨 (이미 캐싱된 상태에서의 메타데이터 크기)
  • 여러 번 로딩해도 안정적으로 캐싱이 유지되는 것을 확인

⚙️ 개발 환경에서는 직접 호스팅 방식 5~11ms 정도 걸리고 TTF 파일로 첫 로딩에 2.0MB가 소요되었지만, 이는 실제 사용자 경험과는 차이가 있다.

📊 각 방식의 장단점

CDN 방식의 장점

  • 초기 설정이 간단하고 바로 적용 가능
  • 폰트 파일 직접 관리할 필요 없음
  • 대부분의 경우 이미 최적화된 포맷(WOFF2)으로 제공
  • 여러 사이트에서 동일 폰트 사용 시 캐싱 효과로 빠른 로딩

CDN 방식의 단점

  • 외부 서비스에 의존하므로 CDN 장애 시 폰트가 적용되지 않을 수 있음
  • 서비스 정책 변경이나 중단 가능성 있음
  • 커스터마이징(서브셋팅 등) 제한적

직접 호스팅 방식의 장점

  • 외부 의존성 없어 안정적
  • 서브셋 등 커스터마이징 자유롭게 가능
  • 폰트 파일에 대한 완전한 제어 가능

직접 호스팅 방식의 단점

  • TTF 파일 사용 시 파일 크기가 클 수 있음
  • 최적화 작업(WOFF2 변환, 서브셋팅 등)이 추가로 필요
  • 폰트 업데이트 시 수동으로 파일 교체 필요

💡 내 프로젝트에 적용할 해결 방안

두 방식 모두 캐싱 이후에는 성능 차이가 거의 없어 보였다. 따라서 선택은 프로젝트 상황과 우선순위에 따라 달라질 수 있다.

✅ 방식 선택 시 고려할 점

  • 첫 방문 사용자 경험: 최적화된 WOFF2 포맷을 바로 사용할 수 있는 CDN이 약간 유리
  • 안정성: 외부 서비스 의존도를 낮추고 싶다면 직접 호스팅이 유리
  • 유지보수: 편의성을 원한다면 CDN이 유리
  • 커스터마이징: 서브셋팅 등 추가 최적화가 필요하면 직접 호스팅이 유리

✅ 상황에 맞는 계획

1. 단기적 해결책: CDN 방식 유지하면서 최적화

  • font-display: swap 추가
  • HTML에 preconnect 추가

2. 장기적 해결책: 직접 호스팅으로 전환하되 최적화 적용

  • TTF 파일을 WOFF2로 변환
  • font-display: swap 추가
  • 필요한 경우 서브셋 폰트 생성

소규모 또는 개인 프로젝트에서는 CDN의 편리함이 큰 장점이고, 대규모 상업 서비스에서는 직접 호스팅의 안정성이 중요할 것 같다.

내 현재 프로젝트에서는 일단 CDN 방식을 유지하면서 font-display: swap을 적용하고, 추후 프로젝트가 성장하게 된다면 직접 호스팅으로 전환하는 계획을 세우는 것이 좋을 것 같다.

👩🏻‍💻💭

웹 폰트 최적화에 대해 공부해 보니, 이전에 그냥 폰트 링크만 넣어서 사용했던 방식에서 많은 개선이 가능하다는 걸 알게 됐다.

특히 한글 폰트는 수천 개의 글리프를 포함하고 있어 파일 크기가 매우 크기 때문에 최적화가 더욱 중요하다. 최적화 방법 중에서도 서브셋팅과 WOFF2 같은 최신 포맷 사용이 가장 효과적이라는 점을 배웠다.

또한 font-display: swap 속성을 설정하면 폰트가 로딩되는 동안 시스템 폰트로 텍스트를 먼저 보여주기 때문에 사용자가 콘텐츠를 바로 볼 수 있어 사용자 경험이 크게 향상된다는 것도 알게 되었다.

CDN vs 직접 호스팅이라는 고민으로 시작했지만, 개발환경에서는 차이가 있었던 두 방식도 프로덕션 환경과 브라우저 캐싱이 적용된 후에는 성능상 큰 차이가 없다는 것을 추가 테스트를 통해 발견했다. 따라서 순수한 로딩 성능보다는 안전성, 유지보수 용이성, 커스터마이징 가능성 등 다른 요소들을 기준으로 프로젝트에 맞는 방식을 선택하는 것이 중요하다.

결국 웹 최적화는 완벽한 해결책보다는 상황에 맞는 트레이드오프를 찾아가는 과정이라는 것을 깨달았다. 내 프로젝트의 규모, 목적, 그리고 우선순위에 따라 적절한 전략을 선택하는 것이 가장 중요한 것 같다.🤓

profile
💭

0개의 댓글