[React] 폰트 최적화 알아보기

인프런 강의 프론트엔드 개발자를 위한, 실전 웹 성능 최적화(feat. React) - Part. 2의 폰트 최적화를 학습하고 정리한 내용입니다.

 

폰트를 최적화하지 않으면 다음과 같은 현상이 발생할 수 있습니다.

May-25-2024 12-24-33.gif

 

웹 폰트도 하나의 리소스로 네트워크를 통해 서버에서 다운로드해야 합니다. 그렇기 때문에 폰트가 적용된 텍스트를 브라우저 화면에 출력할 때 깜빡이는 현상이 발생합니다. 깜빡이는 현상에는 FOUTFOIT가 있다.

 

FOUT(Flash of Unstyled Text)는 처음에는 기본 폰트로 출력하다가 다운로드가 완료되면 폰트가 적용되는 방식입니다. IE와 엣지 등의 브라우저에서 기본적으로 이 방식을 사용하고 있습니다.

FOIT(Flash of Invisible Text)는 처음에는 텍스트를 보여주지 않다가 다운로드가 완료되면 폰트가 적용된 상태로 보여주는 방식입니다. 크롬과 사파리 등의 브라우저에서 기본적으로 이 방식을 사용하고 있습니다.

 

두 가지 방식 중 더 좋은 방식은 없으면 상황에 따라 어떤 방식을 사용할지 결정해야 하고 폰트 최적화를 통해 두가지 현상을 최소화해야 합니다.

 

폰트 적용 시점 컨트롤 하기

font-display CSS 속성을 사용하면 폰트 적용 시점을 컨트롤 할 수 있습니다. 속성의 값으로 auto, block, swap, falllback, optional을 지원합니다.

 

FOIT 방식의 경우 Timeout 이라는 개념이 등장하는 데 지정된 시간까지 다운로드가 완료되지 않으면 기본 폰트로 출력하고 캐시한다는 의미입니다.

import { createGlobalStyle } from 'styled-components';
import BMYEONSUNGTTF from '../assets/fonts/BMYEONSUNG.ttf';

const GlobalStyle = createGlobalStyle`
  @font-face {
    font-family: BMYEONSUNG;
    src: url(${BMYEONSUNGTTF});
    font-display: block;
  }
  .MuiInput-input {
    ...
    font-family: BMYEONSUNG, sans-serif;
  }
`

 

또한, Font Face Observer 등의 라이브러리로 자바스크립트를 통해 폰트 적용 시점을 컨트롤할 수 있습니다.

import { useState, useEffect } from 'react'
import FontFaceObserver form 'fontfaceobserver';

const [isFontLoaded, setIsFontLoaded] = useState(false);

useEffect(() => {
  const font = new FontFaceObserver('BMYEONSUNG');
  
  font.load().then(function() {
    setIsFontLoaded(true);	
  });
}, [])

 

폰트 사이즈 줄이기

웹 폰트 포멧 사용

TTF/OTF가 아닌 WOFF, WOFF2 같은 압축된 웹 전용 폰트를 사용합니다. 폰트 변환 사이트를 통해 폰트 포맷을 변경할 수 있습니다.

다만, 브라우저가 최신 포맷을 지원하지 않을 수도 있기 때문에 다음과 같이 여러 포맷의 우선순위를 지정해줍니다.

import BMYEONSUNGTTF from '../assets/fonts/BMYEONSUNG.ttf';
import BMYEONSUNGWOFF from '../assets/fonts/BMYEONSUNG.woff';
import BMYEONSUNGWOFF2 from '../assets/fonts/BMYEONSUNG.woff2';

@font-face {
   font-family: BMYEONSUNG;
   src: url(${BMYEONSUNGWOFF2}) format('woff2'), 
        url(${BMYEONSUNGWOFF}) format('woff'), 
        url(${BMYEONSUNGTTF}) format('truetype');
   font-display: block;
 }

 

local 폰트 사용

local 함수를 사용하면 폰트 파일이 이미 내 컴퓨터에 저장되어 있는 경우 다운로드 없이 폰트를 사용할 수 있습니다.

@font-face {
   font-family: BMYEONSUNG;
   src: local('BMYEONSUNG'),
        url(${BMYEONSUNGWOFF2}) format('woff2'), 
        url(${BMYEONSUNGWOFF}) format('woff'), 
        url(${BMYEONSUNGTTF}) format('truetype');
   font-display: block;
 }

 

subset 사용

전체 글자 데이터를 폰트 파일에 저장하지 않고 사용하는 글자 데이터만 저장한 폰트 파일로 변환해 폰트 사이즈를 줄인다. 즉, 페이지에서 사용하는 글자 데이터만 남긴다.

@font-face {
   font-family: BMYEONSUNG;
   src: url(${subsetBMYEONSUNGWOFF2}) format('woff2'), 
        url(${subsetBMYEONSUNGWOFF}) format('woff'), 
        url(${subsetBMYEONSUNGTTF}) format('truetype');
   font-display: block;
 }

 

Unicode Range 적용

유니코드 값으로 다운로드할 글자 데이터를 지정합니다.

subset 사용시 페이지에 폰트 파일에 없는 글자가 있어도 불필요하게 폰트를 다운로드합니다. 그러나 Unicode Range를 지정하면 지정한 텍스트가 페이지에 없으면 다운로드하지 않습니다.

@font-face {
   font-family: BMYEONSUNG;
   src: url(${subsetBMYEONSUNGWOFF2}) format('woff2'), 
        url(${subsetBMYEONSUNGWOFF}) format('woff'), 
        url(${subsetBMYEONSUNGTTF}) format('truetype');
   font-display: block;
   unicode-range: u+0041; // A
 }

 

data-url로 변환

data: 스킴이 접두어로 붙은 data-url은 컨텐츠 작성자가 작은 파일을 문서 내에 인라인으로 삽입할 수 있도록 해줍니다. 따라서 페이지에서 극소수의 글자 데이터만 사용하는 경우 data-url을 통해 네트워크 요청 없이 폰트를 사용할 수 있습니다.

@font-face {
   font-family: 'BMYEONSUNG';
   src: url('data:font/woff2;charset=utf-8;base64,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') format('woff2');
}

 

폰트 Preload

폰트 파일은 보통 CSS가 실행될 때 다운로드 됩니다. 그러나 폰트 Preload는 CSS가 실행되기 전에 폰트 파일을 미리 다운로드 합니다.

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

프로덕트 빌드시 번들러에 따라 별도의 작업이 추가적으로 필요할 수도 있습니다.