About
next/font를 활용한 웹폰트 성능 최적화
▶️ next/font 의 핵심 기능
1. 폰트 로딩시간 단축
- cdn 폰트 사용하는 경우(ex. Google-font)
- 빌드 타임에 폰트를 다운로드
- 브라우저단에서 폰트 cdn 으로 요청하지 않음
- 로컬 폰트 사용하는 경우
- 자동으로 self-host on Our Next.js server
- https://localhost:3000/_next/static/media/1asdfisdfjsdflsdji.p.woff2
2. Fallback 폰트가 layout shift 를 발생시키지 않도록 함
- Layout shift
- 폰트 변경에 의해 레이아웃이 변경되는 경우(UX 불편)
- CSS size-adjust 속성을 사용하면 layout shift 없이 로드가능
- adjustFallbackFont 라는 기능을 통해 기존 Fallback 폰트의 size-adjust 속성을 조정하기 때문에 Fallback 폰트와 실제 폰트간의 크기 차이가 발생하지 않는다
- 폰트 때문에 레이아웃이 변경되는 경우가 발견되면 사용하기로..
▶️ Background about Font
1. 웹 폰트란? (출처 링크)
- 웹폰트 : 사용자가 폰트를 설치하지 않아도 디자이너가 원하는 타이포그래피를 웹 페이지에 구현할 수 있게 하는 기술
2. 기본 사용법
- CSS 의 @font-face 규칙을 사용한다
- @font-face 규칙을 사용해 설정한 다음, 웹 폰트가 필요한 선택자의 font-family 속성에서 사용할 웹 폰트의 이름을 호출해서 사용한다
3. 웹폰트의 문제점
- 브라우저 단에서 웹폰트를 다운받고 렌더링함
- 폰트 다운로드가 늦어지는 경우 컨텐츠가 늦게 로딩된다
4. 최적화 방법
1) 폰트파일의 용량 줄이기
- 폰트 패키지 전체를 불러오는 것이 아니라, 사용할 weight 나 파일 형식을 명시하여 필요한 것만 font-face 지정하여 가져온다
@font-face {
font-family : NanumSquareWeb;
src: url(NanumSquareR.woff2) format('woff2'),
url(NanumSquareR.woff) format('woff'),
url(NanumSquareR.ttf) format('trueType')
- 서브셋 폰트
- 폰트파일에서 불필요한 글자를 제거하고 사용할 글자만 남겨둔 폰트
- 한글의 경우 자음&모음 모든 경우를 조합하면 11,172자나 된다.
- 하지만 그중에서 실생활에서 사용하지 않는 폰트들이 많이 있다. (종종 어떤 사이트에서 노란색 형광펜 글씨들은 빈칸으로 렌더링 되지 않는 경우들을 봤을 것)
- 이런 불필요한 글자들은 제거한 것이 서브셋 폰트이다
- 서브셋 폰트가 없는경우 직접 서브셋 폰트를 만들어서 사용할 수도 있다
- 서브셋 폰트는 サブセットフォントメーカー(이하 서브셋 폰트 메이커)나 fontTools 라이브러리를 사용해 만들 수 있다.
- unicode-range 속성
- 명시된 unicode-range 에 해당하는 폰트만 다운받는다
- 폰트별로 다이나믹 서브셋 css 를 제공하기도 함
@font-face {
font-family: 'Pretendard';
font-style: normal;
font-display: swap;
font-weight: 100;
src: url(../../../packages/pretendard/dist/web/static/woff2-dynamic-subset/Pretendard-Thin.subset.42.woff2) format('woff2'), url(../../../packages/pretendard/dist/web/static/woff-dynamic-subset/Pretendard-Thin.subset.42.woff) format('woff');
unicode-range: U+bae6-bafb, U+bafd-bb17, U+bb19-bb33, U+bb37, U+bb39-bb3a, U+bb3d-bb43, U+bb45-bb46, U+bb48, U+bb4a-bb4f, U+bb51-bb53, U+bb55-bb57, U+bb59-bb62, U+bb64-bb8f;
}
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard-dynamic-subset.css");
/**
* 다이나믹 서브셋 폰트(경량화)로 우선 사용해보고 문제시 아래 웹폰트로 대체
* 참고: https://leetaewook.github.io/dynamic-subset-font
*
* @import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard.css");
*/
- 이 외에도 다양한 방법이 존재함 (자세한 내용은 출처 참고)
▶️ Next font 사용법
- Next.js 에서 제공하는 폰트 최적화
1. Next12 vs Nest13 기본 사용법
- next12 에서의 폰트 로드와 next13에서 폰트로드가 어떻게 바뀌었는가
- next12 에서 폰트 사용 방법
export default function Home() {
return (
<div>
<Head>
<title>Font Test</title>
<meta name="description" content="Font Test" />
<link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=Homemade+Apple&display=swap" rel="stylesheet"/>
</Head>
<main>
Homemade Apple Font (Next 12)
<Image src={Cat} alt="cat"/>
</main>
</div>
)
}
- next13 에서 폰트 사용 방법
import { Homemade_Apple } from '@next/font/google';
const homemadeApple = Homemade_Apple({ subsets: ['latin'], weight: ['400'], display: 'swap' })
export default function Home() {
return (
<main className={homemadeApple.className}>
Homemade Apple Font (Next 13)
<Image src={Cat} alt="cat"/>
</main>
)
}
import localFont from '@next/font/local'
const myFont = localFont({src : './my-font.woffw' })
2. Use Case
- 우리는 어떻게 사용할 것인가?
- 사용하는 폰트
- 한글
- Pretendard (600 / 500 / 400)
- 영어
- ABC Monument Grotesk Unlicensed Trial
- Plus Jakarta Sans
- 영문은 유료폰트? 로컬 파일 받을 수 있는건지? 확인 필요
- 한글
1) google-font 설정해보기
- Akshar : Variable font
import { Akshar } from "next/font/google";
const akshar = Akshar({
subsets: ["latin"],
});
<main className={akshar.className}>
<Component {...pageProps} />
</main>,
- Sources 탭에서 확인
- 시스템 폰트 & Akshar 폰트 확인
- Element 탭에서 확인
1-1) font-weight 설정하기
- 글로벌로 폰트가 설정되어있는 상태
- /src/components/page/main/index.tsx
- 자체 <Main> 컴포넌트
<div className={styles.test}>TEST</div>
- ./Main.module.scss
.test {
color: red;
font-weight: 900;
font-style: italic;
}
- 그냥 추가적인 font-weight 를 지정하면 됨
1-2) Google Web Font 사용시 한글 폰트의 subset 사용 이슈
- Noto_Serif_KR 폰트를 사용 (next/font/google 에서 제공)
- 폰트를 세팅하면 기본적으로 subset 을 지정하도록 되어있다. subset 을 지정하지 않을 거면 preload 를 false 로 해야함
(관련 링크 : https://nextjs.org/docs/messages/google-fonts-missing-subsets) - 하지만 subset 에 "korean" 이 없다. 한글 폰트인데, 한글에서 안쓰는 글자를 빼주는 subset 이 제공되지 않음
- subset을 지정하지 않고, preload 를 false 로 세팅하는 경우 영어를 사용할 수 없다는 포스트를 발견
- 테스트 해보기로 함
테스트 결과 영문폰트도 잘 적용이 되었다
subset 과 preload 변경해 가면서 테스트
✅ 영문도 폰트가 잘 적용되어 렌더링된걸 확인할 수 있다.
✅ preload 를 false 로 하는 경우, 특정 subset을 미리 다운받아 놓지만 않는다는 의미
✅ Noto_Serif_KR 이 제공하는 다른 다국어들(한글/라틴/greek/히라가나/가타카나/한자 등등) 을 폰트 적용하는데 문제가 없다.
✅ 영문&다국어&한글 혼용하여 Noto_Serif_KR 을 사용하는 경우, 일단 제공해주는 latin이라도 subset 으로 미리 다운받아놓아서 손해볼것 없음
✅ 미리 다운 안받은 다른 언어들은 그냥 폰트파일 다운 받아 지는대로 적용됨 -> local-font 와 동일한 로직
2) local-font 적용하기
- 글로벌로 pretendard 설정 하기
- Pretendard 공식홈페이지에서 폴더 zip 파일을 다운로드
- 어떤 폰트 파일을 사용할 것인가?
2-1) 가변 폰트 파일 사용 하는 경우
- 하나의 폰트 파일에 다양한 스타일을 가지고 있어, 사용자가 직접 수치를 조정하여 사용
- Pretendard Variable 파일이 있음
- src/styles/fonts/PretendardVariable.woff2 추가
- src/pages/_app.tsx
import localFont from "next/font/local";
const pretendard = localFont({
src: "../styles/fonts/PretendardVariable.woff2",
});
<main className={pretendard.className}>
<Component {...pageProps} />
</main>,
2-2) subset 폰트 파일 사용하는 경우
- Pretendared-1.3.9/web/static/woff2-subset 내부에 woff2 파일들이 있음
- pretendard-subset.css 는 파일명과 weight 매칭 시켜주는 css 파일 -> 참조하여 아래 localFont 의 weight 를 세팅
- src/styles/fonts/PretendardVariable.woff2 추가
- src/pages/_app.tsx
import localFont from "next/font/local";
const pretendard = localFont({
src: [
{
path: "../fonts/helvetica/WOFF2/Pretendard-Black.subset.woff2.woff2", // 14KB
weight: "900",
style: "normal",
},
{
path: "../fonts/helvetica/WOFF2/Pretendard-ExtraBold.subset.woff2.woff2",
weight: "800",
style: "normal",
},
{
path: "../fonts/helvetica/WOFF2/Pretendard-Bold.subset.woff2.woff2",
weight: "700",
style: "normal",
},
{
path: "../fonts/helvetica/WOFF2/Pretendard-Medium.subset.woff2.woff2",
weight: "500",
style: "normal",
},
]
});
<main className={pretendard.className}>
<Component {...pageProps} />
</main>,
Q. 어떤 폰트 파일을 사용할 것인가?
A. Variable 폰트 파일을 사용해도 무방해 보이지만, subset 폰트 파일을 사용하면 다운로드 속도가 월등히 빠르다.
(위의 예시는 fast 3G로 테스트하여 결과가 강조된 경향이 있긴함)
[테스트 화면]
2.1MB 의 variable 폰트를 다운 받을 때 까지 fallback 폰트가 노출되다가, 다운 완료 후 pretendard 로 변경된다
✅ 가능한 subset 폰트를 사용하는게 낫지 않을까 하는 생각
2-3) Header 와 Footer 는 Layout 으로 들어가서 따로 설정해야함
- font 인스턴스를 하나만 쓰고자함
- font 정의용 파일을 하나 뺌
- styles/fonts/index.ts
// font definition file
import localFont from "next/font/local";
const pretendard = localFont({
src: "./PretendardVariable.woff2",
});
export { pretendard };
- Header / Footer
import { pretendard } from "@/styles/fonts";
<footer className={pretendard.className}>
3) 특정 element 만 다른 폰트로 사용하기
- 특정 element 는 Akshar (variable font) 폰트를 사용한다고 가정 (google-font 에서 다운로드)
- styles/fonts/아래 ttf 추가
- fonts/index.ts 에 localFont 인스턴스 추가
const pretendard = localFont({
src: "./PretendardVariable.woff2",
});
const akshar = localFont({
src: "./Akshar-VariableFont_wght.ttf",
});
export { pretendard, akshar };
- <Main> 컴포넌트에서 특정 텍스트만 다른 폰트를 사용해야함
- /src/components/page/main/index.tsx
import { akshar } from "@/styles/fonts";
<div className={`${styles.collectionEng} ${akshar.className}`}>DRESS YOUR TABLE</div>
4) i18n 적용시 국가에 따라 달라지는 텍스트 (참고)
- 영어와 한글 폰트 적용을 어떻게 하는가?
- 전역 폰트 세팅을 달리한다
- _app.tsx
const { lang } = useTranslationCustom();
{getLayout(
<main className={lang === "us" ? akshar.className : pretendard.className}>
<Component {...pageProps} />
</main>,
)}
추가작업
- css 파일에서 따로 지정해줬던 font-family 모두 제거
- button 속성에 user agent stylesheet 로 먹어서 Arial 적용되는 경우?
- globals.css 에서 inherit 로 설정