본문으로 건너뛰기
개발 머꼬
개발 노트Next.js
hohyeon.dev18

구글 폰트를 link 태그로 불러오다가 next/font로 바꾼 이유

  • #Engineering Note
  • #Next.js
  • #Performance

문제 발생

폰트를 CDN에서 <link>로 불러오고 있었습니다. 첫 로드에서 글자가 시스템 폰트로 잠깐 보였다가 웹폰트로 바뀌면서 줄바꿈 위치가 달라졌고, 그만큼 아래 내용이 밀렸습니다.

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet" />

원인 분석

문제가 세 겹입니다.

네트워크 왕복. CSS를 먼저 받아야 폰트 파일 URL을 알 수 있으므로 요청이 순차적으로 두 번 일어납니다. 다른 도메인이라 DNS·TLS 협상도 별도입니다.

레이아웃 이동. 대체 폰트와 웹폰트의 글자 폭이 다르면 교체되는 순간 줄바꿈이 바뀝니다. display: swap은 글자가 안 보이는 시간을 줄여 주지만 이동 자체를 없애지는 않습니다.

프라이버시. 사용자의 브라우저가 폰트 제공자에게 직접 요청을 보냅니다.

next/font는 셋을 한 번에 다룹니다. 빌드 시점에 CSS와 폰트 파일을 내려받아 나머지 정적 자산과 함께 셀프 호스팅하므로 브라우저가 구글에 아무 요청도 보내지 않습니다. 그리고 대체 폰트의 메트릭을 실제 폰트에 맞춰 조정해(adjustFontFallback, 기본값 켜짐) 교체 시 레이아웃이 흔들리지 않게 합니다.

해결 방안

  1. 루트 레이아웃에서 폰트를 정의하고 className으로 적용합니다. 가변 폰트라면 weight를 지정할 필요가 없습니다.
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"], display: "swap" });

export default function RootLayout({ children }) {
  return (
    <html lang="ko" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}
  1. subsets를 지정합니다. 프리로드가 켜져 있는데(기본값 true) 서브셋을 지정하지 않으면 경고가 납니다. 한글 폰트라면 파일이 크므로 어떤 서브셋을 미리 받을지가 특히 중요합니다.
  2. CSS 변수로 쓰면 Tailwind와 붙기 쉽습니다. 이 저장소처럼 토큰으로 폰트를 관리한다면 이쪽이 맞습니다.
const inter = Inter({ subsets: ["latin"], variable: "--font-sans", display: "swap" });
// <html className={inter.variable}> ... 그리고 CSS에서 var(--font-sans)
  1. 로컬 폰트도 같은 방식입니다. 웹에서 받을 수 없는 폰트는 next/font/local로 파일을 지정합니다. 저장소에 폰트 파일을 두게 되지만, 셀프 호스팅과 메트릭 조정의 이점은 동일합니다.
  2. 폰트 정의는 한 파일에 모읍니다. 폰트 함수를 호출할 때마다 인스턴스가 하나씩 만들어지므로, 여러 곳에서 같은 폰트를 쓴다면 한 곳에서 정의하고 import합니다.
  3. 폰트 수를 늘리지 않습니다. 공식 문서의 권고이기도 합니다 — 폰트 하나가 늘 때마다 클라이언트가 받아야 할 리소스가 하나 늘어납니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.