매번 프로젝트를 시작할 때 어떻게 하면 폰트 설정을 ‘잘’ 할 수 있는지에 대해 고민한 적이 종종 있었다. 하지만 여유가 없어 매번 해왔던 방식으로 로컬에 다운을 받아 사용하거나 혹은 웹폰트를 사용해 진행을 해왔었다.
그 결과, 브라우저가 초기에 띄워질 때 잠깐 동안 폰트가 적용되지 않는 현상이 일어나곤 했다. 이 현상을 FOUT
이라고 하는데, 이와 관련해서 더 자세하게 알아보자.
📌 FOUT와 FOIT
FOUT(Flash of Unstyled Text)
FOIT(Flash of Invisible Text)
아래 내용은 참고하면 좋을 것 같아 가져와보았다.
CSS font-display 설정
auto
: 브라우저의 기본 동작
block
: FOIT
swap
: FOUT
fallback
: 100ms동안 텍스트가 보이지 않고 그 후 렌더링
optional
: 네트워크 상태에 맞게 웹폰트 전환 여부 판단
이로써 내가 겪었던 현상은 FOUT이라고 정의할 수 있다. 폰트가 로드되는 속도가 느리기 때문에 페이지 로드 속도 또한 느려지게 되는 현상이 발생한 것이었다.
❓이러한 현상이 왜 일어나는 걸까?
이런 현상을 이해하기 위해선 브라우저의 동작 원리부터 잘 알고 있어야 한다.
위 아티클에 폰트와 관련된 브라우저 동작 원리가 잘 나와있다.
요약을 해보자면,
⇒ CSSOM을 DOM트리와 결합시켜 렌더링 트리를 구성하는 시점에 폰트 리소스를 요청하게 된다.
⇒ 폰트 리소스 요청은 다른 리소스 요청보다 늦게 진행되기 때문에 리소스 응답이 늦어지게 되어 FOUT
, FIOT
과 같은 현상이 발생하게 된다.
❓이러한 현상을 어떻게 해결할 수 있을까
찾아보니 여러 가지 방법이 있는 것 같다.
1) 웹폰트 리소스 preload
위에서 이야기한 내용과 직결되는 대안일 수 있겠다. 폰트 리소스 요청이 타 리소스보다 늦게 시작되기 때문에 시점을 앞당기는 방안이다. 즉, preload
를 사용해 초기에 요청하는 것이다.
<link rel="preload" href="...woff2" as="font" type="font/woff2" ... />
다음과 같이 <head>
에서 폰트를 요청할 때 rel=”preload”
를 추가하면 된다고 한다. 폰트파일을 미리 다운로드하고 나중에 사이트에 재방문 시 폰트를 새로 렌더링하지 않아도 되기 때문에 최적화가 가능해보인다.
흠..
하지만 단점이 있다! 페이지에 필요한 폰트를 preload해야 하는데, 사용하지 않는 폰트를 preload에 적용하게 되면 로딩 시간이 길어진다고 한다.
결국 최종적으로 문제점을 해소해줄 수 있는 방안은 아닌 듯하다.
2) 용량이 작은 웹폰트 확장자 사용하기
아마 폰트에 대해 찾아본 이들은 한번쯤은 들어봤을 확장자들이 있다.
여러 브라우저에서 지원되고 있는 폰트 포맷이다. 여기서 용량이 큰 순서부터 나열하면 TTF > EOT > WOFF > WOFF2 순이다. WOFF2가 압축률이 가장 좋다고 한다.
3) 서브셋 폰트 사용하기
서브셋 폰트는 폰트에서 불필요한 글자를 제거한 폰트 파일을 뜻한다. 실제 서비스에서 사용하지 않는 조합들을 제거해서 만든 것이 서브셋 폰트이기 때문에, 폰트의 용량을 줄여 최적화가 가능하다.
4) [next 한정] next/font
사용하기
next.js에서 개발을 진행 중인 나는 next/font를 사용함으로써 최적화를 진행하려고 한다.
요게 넥스트 공식 문서에 나와 있는 폰트 최적화 방법이다.
본격적으로 공식 문서를 뜯어보자!
📌 Font Optimization in NEXT.js
공식 문서를 잘 살펴보면 다음과 같이 정리할 수 있다.
코드와 함께 살펴보자.
공식 문서에 나와있는 코드를 그대로 작성하는 건 큰 의미가 없어보여 현재 진행 중인 프로젝트 코드를 가져와 next/font
를 활용해 수정해보겠다.
우리 프로젝트에서는 variable fonts에 없는 pretendard를 사용하고 있기 때문에 따로 정의가 필요하다.
// src > util > fonts.ts
import localFont from "next/font/local";
const pretendard = localFont({
src: [
{
path: "../../public/fonts/Pretendard-SemiBold.woff",
weight: "700",
},
{
path: "../../public/fonts/Pretendard-Medium.woff",
weight: "500",
},
],
variable: "--font-pretendard",
});
export default pretendard;
다음과 같이 localFont
를 사용하여 pretendard라는 폰트를 선언할 수 있다. src 안에서 사용하고자 하는 font-weight들을 woff파일로 지정하면 된다.
그리고 우리 프로젝트에선 현재 tailwind를 사용하고 있기에 tailwind.config.ts에서의 수정이 필요하다.
// tailwind.config.ts
...
fontFamily: {
pretendard: ["var(--font-pretendard)"],
},
...
다음과 같이 속성을 넣어주게 되면, font-pretendard
사용이 가능해진다.
마지막으로 레이아웃에서 적용해준다.
// src > app > layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className={`${pretendard.variable} font-pretendard`}>
...
</html>
);
}
${pretendard.variable}
실제로 사용자의 브라우저에서 로드되는 폰트 경로를 나타낸다.
localFont 함수를 사용해 정의된 변수이기 때문에 폰트 파일의 경로를 나타내는 것이다.
⇒ 웹 페이지가 로드될 때 브라우저가 해당 경로에서 폰트를 가져오게 된다.
⇒ 폰트를 동적으로 로드하기 위한 변수라고도 할 수 있다.
font-pretendard
요기서 잠깐..!
❓왜 폴백 폰트가 필요한 걸까?
우선 폴백 폰트의 정의는 다음과 같다.
즉, 폴백 폰트는 웹페이지가 예상대로 showing할 수 있도록 하는 데에 도움이 된다고 말할 수 있다.
웹 개발을 해본 사람이라면, 아래 사진과 같이 보통 폰트 목록을 지정할 때, 여러 폰트가 차례로 나열되어 있는 걸 많이 봤을 것이다. 브라우저는 목록에서 첫 번째로 지정된 폰트부터 차례로 시도하게 된다. 만약 해당 폰트를 사용할 수 없는 경우 다음 폰트로 넘어가게 된다.
이러한 방식으로 웹 페이지가 다양한 환경에서 적절하게 렌더링될 수 있도록 보장할 수 있는 것이다.
항상 개발하면서 왜 이렇게 많이 쓰나 궁금했었는데.. 해결되었다!! 와~
정리하자면!
폴백 폰트는 웹 페이지의 일관된 모양과 느낌을 유지하고 UX를 개선하는 데에 중요한 역할을 한다.
[참고한 글]
폰트가 어떻게 동작하는지에 대해 잘 나와있는 것 같다!
이전까진 폰트 관련해서 제대로 정리해본 적이 없었기에 이번 포스팅은 유독 많은 인사이트를 얻을 수 있었던 것 같다. 그리고 이렇게 폰트 최적화 방법이 다양한 줄 몰랐었다.
역시 난 우물 안 개구리! ^,^ 더 많이 공부해야겠다~
폰트 및 환경 세팅부터 다시 리팩토링을 시작하고 있는데 너무 즐겁다!
앞으로도 계속 이렇게 즐겁게 작업할 수 있었으면 좋겠당 😈