frontend

FOUT 현상 완벽 가이드: 사용자 경험을 해치지 않는 웹 폰트 최적화와 페이드인 전략

웹 서비스의 첫인상을 결정짓는 중요한 요소 중 하나는 타이포그래피입니다. 하지만 커스텀 폰트가 로드되기 전, 시스템 기본 폰트가 노출되다가 갑자기 글꼴이 바뀌며 레이아웃이 뒤틀리는 현상을 마주한 적이 있을 겁니다. 이를 **FOUT(Flash of Unstyled Text)**라고 부릅니다. 오늘은 이 현상의 기술적 배경과 이를 우아하게 해결하는 실무 전략을 공유하겠습니다.

목차

  1. FOUT 현상이란 무엇인가?
  2. FOUT vs FOIT: 폰트 로딩의 딜레마
  3. 웹 폰트 최적화를 위한 기술적 접근
  4. 우아한 해결책: 초기 로딩 페이드인(Fade-in) 방식
  5. 개발자의 팁: 폰트 파일 자체의 최적화

FOUT 현상이란 무엇인가?

**FOUT(Flash of Unstyled Text)**는 브라우저가 웹 폰트를 다운로드하기 전까지 시스템 기본 폰트(fallback font)로 텍스트를 먼저 렌더링하고, 다운로드가 완료되면 커스텀 폰트로 교체하는 현상을 말합니다.

이 과정에서 폰트 간의 자간(letter-spacing), 높이(line-height) 차이로 인해 **레이아웃 시프트(Layout Shift)**가 발생하며, 이는 구글의 성능 지표인 CLS(Cumulative Layout Shift) 점수에 악영향을 미칩니다. 사용자 입장에서는 글자가 ‘깜빡’이거나 ‘덜컥’이는 불쾌한 경험을 하게 됩니다.

FOUT vs FOIT: 폰트 로딩의 딜레마

브라우저마다 폰트를 처리하는 방식이 다릅니다.

  • FOIT (Flash of Invisible Text): 폰트가 로드될 때까지 텍스트를 아예 보여주지 않습니다. (주로 Safari, Chrome 등에서 기본 동작) 텍스트가 안 보이면 사용자는 콘텐츠가 없다고 판단하고 이탈할 수 있습니다.
  • FOUT: 일단 보여주지만 스타일이 적용되지 않은 상태입니다.

현대 웹 표준에서는 텍스트의 가독성(Readability)을 우선시하여 FOUT를 허용하되, 그 차이를 최소화하는 방향으로 발전하고 있습니다.


웹 폰트 최적화를 위한 기술적 접근

가장 먼저 적용해야 할 것은 CSS의 font-display 속성입니다.

/* 가장 권장되는 방식 */
@font-face {
  font-family: 'Pretendard';
  src: url('/fonts/Pretendard-Regular.woff2') format('woff2');
  font-display: swap; /* 폰트 로딩 중에는 기본 폰트 노출, 완료 후 교체 */
  font-weight: 400;
}

**font-display: swap**은 텍스트가 항상 보이도록 보장하지만, 여전히 폰트 교체 시점의 이질감은 남습니다. 이를 보완하기 위해 브라우저의 폰트 로딩 API를 활용하거나 미리 불러오기(preload)를 병행해야 합니다.


우아한 해결책: 초기 로딩 페이드인(Fade-in) 방식

폰트 교체 시 발생하는 ‘계층 현상(레이아웃이 층을 이루며 변하는 현상)‘은 완벽히 막기 어려울 때가 많습니다. 이를 기술적으로 해결하는 대신 사용자 경험(UX) 관점에서 우아하게 풀어내는 방법이 바로 초기 로딩 페이드인 방식입니다.

웹 페이지가 처음 로드될 때 화면 전체나 텍스트 영역을 투명하게 유지하다가, 핵심 폰트 로딩이 완료된 시점에 부드럽게 보여주는 전략입니다.

구현 예제 (TypeScript & CSS)

import { useEffect, useState } from 'react';

export const FadeInContent = ({ children }: { children: React.ReactNode }) => {
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    // document.fonts API를 사용하여 폰트 로딩 상태를 감지합니다.
    document.fonts.ready.then(() => {
      setIsLoaded(true);
    });
  }, []);

  return (
    <div className={isLoaded ? 'opacity-100 transition-opacity duration-700' : 'opacity-0'}>
      {children}
    </div>
  );
};

이 방식의 장점은 다음과 같습니다:

  1. 시각적 안정성: 폰트가 바뀌며 레이아웃이 흔들리는 과정을 사용자에게 노출하지 않습니다.
  2. 보안성 및 완성도: 미완성된 페이지(폰트가 깨진 상태)를 노출하지 않아 서비스의 신뢰도를 높입니다.
  3. 의도된 지연: 로딩 속도가 충분히 빠르다면 사용자는 자연스러운 진입 애니메이션으로 인지하게 됩니다.
Implementation of a fade-in animation using opacity transitions to hide FOUT during initial web font loading

개발자의 팁: 폰트 파일 자체의 최적화

아무리 페이드인 처리를 해도 폰트 파일이 너무 크면 페이드인 대기 시간이 길어져 사용자 이탈이 발생합니다.

Tip: 폰트 용량을 줄이기 위해 다음 두 가지를 반드시 확인하세요.

  1. WOFF2 사용: WOFF 대비 30~50% 더 나은 압축률을 가집니다.
  2. Subset 폰트 제작: 사용하지 않는 한자나 특수문자를 제거한 서브셋 폰트를 사용하면 용량을 MB 단위에서 KB 단위로 줄일 수 있습니다. (예: font-spider 활용)

자주 묻는 질문 (FAQ)

Q1. 페이드인 방식을 쓰면 LCP(Largest Contentful Paint) 점수가 나빠지지 않나요?

네, 의도적으로 요소를 가리기 때문에 수치상으로는 미세하게 불리할 수 있습니다. 하지만 폰트가 덜컥거리며 CLS 점수를 깎아먹는 것보다, 부드러운 전환을 통해 사용자 체감 성능(Perceived Performance)을 올리는 것이 실제 UX 측면에서는 훨씬 유리합니다.

Q2. 모든 폰트에 preload를 걸어도 될까요?

아니요. preload는 브라우저의 대역폭을 우선적으로 점유하므로, 가장 먼저 보여야 하는 헤드라인 폰트 1~2개에만 적용하는 것이 좋습니다. 모든 폰트에 적용하면 오히려 초기 JS 번들 로딩이 늦어질 수 있습니다.

Q3. font-display: optional은 어떤 경우에 쓰나요?

매우 짧은 시간(약 100ms) 내에 로드되지 않으면 그냥 시스템 폰트를 그대로 쓰도록 강제하는 속성입니다. 폰트 교체로 인한 레이아웃 시프트를 절대 허용하고 싶지 않을 때 사용합니다.

FOUT 현상은 단순히 스타일의 문제를 넘어 웹 서비스의 전문성을 좌우하는 요소입니다. 기술적인 최적화와 함께 페이드인 전략과 같은 우아한 대응을 섞어준다면, 사용자에게 훨씬 더 견고한 첫인상을 심어줄 수 있을 것입니다!

이 글이 마음에 드셨나요?

로딩 중...