웹사이트에 접속했을 때, 처음에는 기본 폰트로 텍스트가 보이다가 갑자기 예쁜 웹 폰트로 바뀌면서 줄 바꿈이 변하거나 문단이 아래로 툭 떨어지는 현상을 겪어보신 적이 있나요?
사용자를 당황하게 만드는 이 현상을 FOUT(Flash of Unstyled Text)라고 하며, 이는 구글의 핵심 성능 지표 중 하나인 CLS(Cumulative Layout Shift) 점수를 크게 갉아먹는 주범입니다. 오늘은 웹 폰트 로딩 최적화를 통해 시각적 안정성을 확보하는 실무 전략을 깊이 있게 알아보겠습니다.
1. CLS의 적, 웹 폰트 로딩의 이해
웹 폰트는 용량이 크기 때문에 브라우저가 HTML과 CSS를 파싱하는 속도보다 늦게 다운로드되는 경우가 많습니다. 이때 브라우저는 두 가지 방식 중 하나를 선택합니다.
- FOIT (Flash of Invisible Text): 폰트가 다운로드될 때까지 텍스트를 아예 보여주지 않음. (사용자는 빈 화면을 보게 됨)
- FOUT (Flash of Unstyled Text): 시스템 기본 폰트로 먼저 보여준 뒤, 웹 폰트가 로드되면 교체함. (이 과정에서 레이아웃이 어긋남)
구글은 사용자 경험을 위해 텍스트가 아예 안 보이는 것보다 레이아웃이 조금 흔들리더라도 빨리 보이는 것을 권장하지만, 이 '흔들림'을 최소화하는 것이 프론트엔드 개발자의 실력입니다.
2. font-display 속성으로 로딩 제어하기
가장 쉽고 필수적인 해결책은 CSS의 @font-face에서 font-display 속성을 사용하는 것입니다.
@font-face {
font-family: 'Pretendard';
src: url('/fonts/Pretendard.woff2') format('woff2');
font-display: swap; /* 핵심 설정 */
}
2.1 주요 옵션 차이점
- block: 폰트가 로드될 때까지 텍스트를 숨깁니다(최대 3초).
- swap (권장): 시스템 폰트로 즉시 텍스트를 보여주고, 웹 폰트 로드가 완료되면 교체합니다. LCP 점수 유지에 유리합니다.
- fallback: 아주 짧은 시간(약 0.1초) 동안 텍스트를 숨긴 후 로드되지 않으면 기본 폰트를 보여줍니다.
- optional: 네트워크 상태에 따라 웹 폰트를 쓸지 말지 브라우저가 결정합니다. 성능이 최우선일 때 사용합니다.
3. 가상 폰트(Fallback Font) 최적화: 사이즈 매칭
font-display: swap을 사용하더라도 기본 폰트와 웹 폰트의 글자 크기, 자간, 높이가 다르면 여전히 레이아웃 시프트가 발생합니다. 이를 해결하기 위해 최근에는 가상 폰트(Size Adjust) 기법을 사용합니다.
3.1 size-adjust와 ascent-override
웹 폰트와 최대한 비슷한 부피를 가지도록 시스템 폰트의 수치를 강제로 조정하는 방식입니다.
/* 기본 폰트(Arial)를 웹 폰트와 크기가 비슷하게 조정 */
@font-face {
font-family: 'Pretendard-Fallback';
src: local('Arial');
size-adjust: 95%; /* 전체 크기 조정 */
ascent-override: 90%; /* 글자 윗부분 높이 조정 */
descent-override: 10%; /* 글자 아랫부분 높이 조정 */
}
body {
font-family: 'Pretendard', 'Pretendard-Fallback', sans-serif;
}
이렇게 하면 폰트가 교체될 때 텍스트의 부피 변화가 거의 없어 CLS 점수를 0에 가깝게 유지할 수 있습니다. 최근에는 Capsize 같은 도구를 통해 이 수치를 정교하게 계산할 수 있습니다.
4. Next.js를 사용한다면? next/font 활용
Next.js (v13+) 환경에서 개발 중이라면 위 복잡한 과정이 대폭 자동화됩니다. next/font는 빌드 시점에 폰트를 최적화하고 가상 폰트를 자동으로 생성해 줍니다.
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
adjustFontFallback: true, // 가상 폰트 자동 생성 옵션
});
export default function RootLayout({ children }) {
return (
<html lang="ko" className={inter.className}>
<body>{children}</body>
</html>
);
}
next/font를 사용하면 폰트 파일을 구글 서버가 아닌 우리 서버(Self-hosting)에서 직접 제공하므로 개인정보 보호와 성능 면에서 모두 유리합니다.
5. 실무 체크리스트: 웹 폰트 100% 활용법
- 포맷 확인: 가장 압축률이 좋은 woff2 포맷을 우선적으로 사용하세요.
- Subset 활용: 한글 폰트는 용량이 매우 큽니다. 자주 쓰는 2,350자만 추린 '서브셋(Subset)' 폰트를 사용해 용량을 80% 이상 줄이세요.
- Preload: 중요한 폰트는 <link rel="preload">를 통해 미리 로드하세요.
-
HTML
<link rel="preload" href="/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin> - 로컬 폰트 활용: 사용자의 기기에 이미 설치된 폰트가 있다면 그것을 우선 사용하게 설정하세요. (src: local('Pretendard'), url(...))
'개발' 카테고리의 다른 글
| [React] 선언적 프로그래밍의 정수: Suspense와 ErrorBoundary로 우아한 UI 만들기 (0) | 2026.04.27 |
|---|---|
| [React] Props Drilling을 피하는 3가지 방법: Composition vs Context vs Zustand (0) | 2026.04.27 |
| [JS] 메인 스레드를 비워라! Web Worker를 활용한 무거운 연산 분산 처리 (0) | 2026.04.27 |
| [Next.js] LCP 점수를 높이는 이미지 최적화 전략: next/image 완벽 활용법 (0) | 2026.04.27 |
| [React] useMemo와 useCallback은 언제 정말로 성능을 개선할까? (측정 기준) (0) | 2026.04.27 |