구글 폰트를 link 태그로 불러오다가 next/font로 바꾼 이유
문제 발생
폰트를 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, 기본값 켜짐) 교체 시 레이아웃이 흔들리지 않게 합니다.
해결 방안
- 루트 레이아웃에서 폰트를 정의하고 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>
);
}subsets를 지정합니다. 프리로드가 켜져 있는데(기본값true) 서브셋을 지정하지 않으면 경고가 납니다. 한글 폰트라면 파일이 크므로 어떤 서브셋을 미리 받을지가 특히 중요합니다.- CSS 변수로 쓰면 Tailwind와 붙기 쉽습니다. 이 저장소처럼 토큰으로 폰트를 관리한다면 이쪽이 맞습니다.
const inter = Inter({ subsets: ["latin"], variable: "--font-sans", display: "swap" });
// <html className={inter.variable}> ... 그리고 CSS에서 var(--font-sans)- 로컬 폰트도 같은 방식입니다. 웹에서 받을 수 없는 폰트는
next/font/local로 파일을 지정합니다. 저장소에 폰트 파일을 두게 되지만, 셀프 호스팅과 메트릭 조정의 이점은 동일합니다. - 폰트 정의는 한 파일에 모읍니다. 폰트 함수를 호출할 때마다 인스턴스가 하나씩 만들어지므로, 여러 곳에서 같은 폰트를 쓴다면 한 곳에서 정의하고 import합니다.
- 폰트 수를 늘리지 않습니다. 공식 문서의 권고이기도 합니다 — 폰트 하나가 늘 때마다 클라이언트가 받아야 할 리소스가 하나 늘어납니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.