해당 성능 최적화 포스팅은 여러 주제로 나누어 시리즈로 작성 할 계획이다.
이전에 진행했던 프로젝트에서 시간이 급급한 나머지 놓쳤던 성능 최적화 부분에 대해 리팩토링을 해 보았다.
우선 유저에게 가장 먼저 보여지는 화면을 개발하는 프론트엔드 개발자는 유저에게 제공하려는 웹 사이트를 빠르게 보여줘야하고, 사용자의 동작에 따른 빠른 인터랙션을 제공해야한다. 즉, 좋은 사용자 경험(UX)을 제공 해야하는 것이다.
아래는 구굴이 공개한 시간대별 페이지 이탈률로 페이지 로딩 시간이 1초에서 3초로 증가하면 페이지 이탈률이 32%로 증가한다고 하고, 1초에서 10초가 되면 페이지 이탈률이 123%나 된다고한다.
프론트엔드 성능 최적화에는 성능 최적화 외에도 접근성, SEO 등 다양한 부분이 있지만 우선 성능 최적화에 초점을 맞추어 리팩토링 하고 난 뒤 SEO도 건드려 봐야겠다.
우선 Font 최적화에 대해 알아보자.
이전에 진행했던 대박 사건 프로젝트에서는 @font-face를 사용하여 자체 호스팅 방식으로 font를 적용했다.
interface ThemeProps {
[key: string]: string;
}
const global = (theme: ThemeProps) => css`
*,
body {
font-family: 'PretendardRegular, sans-serif';
}
// 부분 생략
@font-face {
font-family: 'PretendardRegular';
src:
local('PretendardRegular'),
url(${PretendardRegular}) format('woff2');
font-style: normal;
font-display: swap;
}
`;
export default global;
이 코드를 토대로 빌드 후 preview를 돌려 lighthouse로 성능 측정을 해 보았는데 아래와 같은 문구가 보여졌다.
이게 도대체 무슨 말일까. 나는 font를 잘 사용하고 있던게 아니였나? 라는 의문이 들기 시작했고 해당 부분에 대해 알아보기 시작했다.
우선 'font-display에 관해 자세히 알아보기'를 클릭하여 해당 링크를 타고 들어가 읽어 보았고, 해당 링크에서 FOIT에 대한 개념을 알게 되었다.
그렇다면 FOIT는 도대체 무엇일까?
FOIT는 웹 Font를 불러오는 과정에서 브라우저가 폰트를 렌더링하기 전까지 텍스트가 보이지 않는 현상을 말한다.
이는 폰트를 불러오는 시간이 길어지는 상황에 발생할 수 있고, 사용자가 폰트가 로드될 때가지 기다려야 하므로 유저에게 사용성을 해칠 수 있다.
이러한 FOIT 방식 브라우저에서 FOUT 방식처럼 동작하도록 구현할 수 있는데, 그 방법이 바로 font-display 속성이다.
font-display의 기본값은 auto로 브라우저가 최적의 방법으로 웹 폰트를 로드하는 것이다. 즉, 각각의 브라우저마다 기본 동작이 다르다는 것인데, 각각 Chrome과 Safari에서 어떻게 동작하는지 봐보자. 아래는 각각 Chrome과 Safari의 네트워크 환경을 3G로 세팅한 뒤 실행 한 화면이다.
각각의 동작을 보면 Chrome에서는 기본 폰트를 먼저 보여준 뒤 우리가 사용하려는 폰트가 다운로드가 되면 갈아 끼워주고 있고, Safari 같은 경우네는 기본 폰트 마저 보여주지 않고 있다가, 기본 폰트가 내려 받아지면 보여주고 그 후 우리가 사용하려는 폰트가 로드 될 때까지 기다렸다가 로드가 되고 나면 다시 갈아 끼워주는 모습을 볼 수 있다.
이는 각각의 브라우저의 font-display: auto가 동작 방식이 달라서 그렇다. 그렇다면 더욱 auto를 사용하면 안되겠다는 생각이든다.
그렇다면 font-display의 다양한 속성들을 확인해보자.
위 사진은 MDN 공식문서에 나와있는 font-display 속성 정의이다. 이렇게만 봐서는 무슨 뜻인지 잘 모르겠다.
좀 더 자세한 설명을 위해 https://drafts.csswg.org/css-fonts/#font-display-desc 문서를 참고했다.
font-display: block
글꼴에 짧은 블록 기간(대부분의 경우 3초 권장)과 무한 스왑 기간을 부여한다. 즉, 브라우저가 로드되지 않은 경우 처음에는 "보이지 않는" 텍스트를 그리지만 로드되는 즉시 글꼴을 교체한다.
이 속성은 주로 아이콘 글꼴을 사용할 때 사용된다. 예를 들어 📎 이러한 글꼴 아이콘이 나타나야 할 때 전혀 관련이 없는 clip의 c가 아이콘 글꼴 보다 먼저 나타나게 된다면 유저 입장에서 보기 좋지 않을 것이다.
font-display: swap
폰트에 매우 짧은 블록 주기(대부분의 경우 100ms 이하 권장)와 무한한 스왑 주기를 부여한다. 즉, 폰트가 로드되지 않은 경우 브라우저는 fallback을 사용하여 즉시 기본 텍스트를 그리지만 로드되는 즉시 폰트를 교체한다.
이 속성은 주로 웹사이트에 텍스트를 렌더링하는 사용자 정의 폰트가 있는 경우, 해당 텍스트를 올바르게 렌더링하는 것도 중요하지만, 어떤 폰트로든 텍스트를 표시하면 적어도 혼동 없이 요점을 전달할 수 있다.
font-display: fallback
폰트에 매우 짧은 블록 주기(대부분의 경우 100ms 이하 권장)와 짧은 스왑 주기(대부분의 경우 3초 권장)를 부여한다. 즉, 폰트 페이스가 로드되지 않은 경우 처음에는 폴백으로 렌더링되지만 로드되는 즉시 교체된다. 그러나 너무 많은 시간이 경과하면 페이지의 나머지 수명 동안 대체 폰트가 대신 사용됩니다.
이 속성은 사용자가 중요한 텍스트를 읽을 때, 폰트 교체로 인해 텍스트가 갑자기 변경되거나 이동 되는 것을 피하는 것이 중요한데, 이를 방지하려면 텍스트가 이미 표시된 후 폰트를 바꾸는 것 보다 사용자가 읽기 시작하기 전에 선택한 폰트가 로드되어 사용자의 읽기 흐름을 방해하지 않을 때 사용한다
font-display: optional
폰트를 즉시 로드할 수 있는 경우(텍스트의 "첫 번째 페인트"에 사용할 수 있는 경우) 해당 폰트가 사용된다. 그렇지 않으면 폰트 로딩이 완료되기 전에 차단 기간과 교체 기간이 모두 만료된 것으로 처리된다. 이로 인해 폰트가 사용되지 않으면 사용자 에이전트는 폰트 다운로드를 중단하거나 매우 낮은 우선순위로 다운로드 하도록 선택할 수 있다. 사용자 에이전트가 대체 폰트를 사용하는 것이 유용하다고 판단하면 폰트 다운로드를 시작하지 않고 즉시 대체 폰트를 사용할 수 있다.
나는 이미 폰트 확장자로 wbff2를 사용하고 있었기에 최적화가 되어 있었고, 위 속성 중 font-display: swap을 사용하여 폰트가 로드 되기 전, 기본 폰트로 텍스트를 먼저 보여준 뒤 폰트 다운로드가 완료되면 그때 교체 될 수 있게 해주었다. 그 이유는 우리 서비스는 폰트가 어떻든 편지, 댓글, 메인 페이지의 우리 서비스 소개 등등 빠르게 정보를 전달 해주는 것이 더 사용자의 경험을 높힐 수 있다고 생각했기 때문이다.
이로써 FOIT -> FOUT 방식으로 변경이 되었다. 그렇다면 FOUT는 무엇일까?
FOUT란?
FOUT이란 웹 폰트가 로딩될 때까지 우선 fallback 폰트로 텍스트를 렌더링하고, 웹 폰트 로딩이 완료되면 텍스트를 웹 폰트로 전환한다. 이 방식은 웹 폰트 로딩 여부에 관계없이 텍스트가 항상 보이는 장점이 있다. 하지만 글꼴의 자간, 높이 등 서식이 달라 웹 폰트 적용 전과 후에 레이아웃이 변경될 수 있다는 단점도 존재한다.
이는 빠르게 렌더링되어 FCP및 LCP의 성능을 향상 시킬 수 있다는 장점이 있으며, FCP(First Contentful Paint)란 브라우저가 페이지의 첫 번째 요소(일반적으로 텍스트)를 렌더링한 시점이다. 사용자가 페이지를 요청한 후 FCP까지 걸리는 시간이 짧을수록 페이지는 빠르게 로드된 것으로 간주되며, LCP(Largest Contentful Paint)란 페이지에서 가장 큰 요소(일반적으로 이미지 또는 비디오)가 렌더링된 시점이다. LCP 시간은 페이지의 로딩 시간과 사용자 경험에 큰 영향을 미친다.
이로써 사파리에서도 텍스트가 블록되지 않고, 먼저 기본 폰트로 텍스트를 보여준 뒤 원하는 폰트가 다운로드가 되고나면 교체되게 되었다.
또한, "PretendardRegular" 글꼴을 우선적으로 사용하도록 브라우저에 지시하고, 사용할 수 없는 경우 일반 sans-serif 글꼴을 대체 글꼴로 사용하도록 설정하여 LCP에 영향을 미치던 "콘텐츠가 포함된 최대 페인트 요소"를 빠르게 렌더링하도록 하여 해결함으로써 lighthouse 성능 점수를 93 -> 100점으로 높힐 수 있었다.
'React' 카테고리의 다른 글
렌더링을 빨리 빨리!! by 번들 사이즈 최적화 (2) | 2024.04.25 |
---|---|
프론트엔드 성능 최적화(Image 압축 및 확장자 변경 자동화) (0) | 2024.04.13 |
human error를 줄여 동료 개발자의 경험을 개선해보자! with console.log() (0) | 2024.04.08 |
OAuth 로그인 구현기 (2) | 2024.04.04 |
공통 컴포넌트에 유연성을 확장 시켜보자! by 합성 컴포넌트 패턴 (1) | 2024.03.31 |
같은 실수를 반복하지 않고, 한 번 학습한 내용을 오래 기억하기 위해 개발하면서 겪었던 트러블 슈팅과 학습한 내용을 정리하고 기록합니다 🧑💻