제가 만든 두 프로젝트가 폰트를 완전히 다르게 불러오고 있다는 걸 뒤늦게 깨닫고 비교해봤어요. media-crop 저장소layout.tsx랑, 이 사이트(Frontend Note) 자체의 index.html 폰트 로딩 방식이 서로 다르더라고요.

media-crop: next/font로 빌드 시점에 자체 호스팅

import { Geist, Geist_Mono } from "next/font/google";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

next/font/google은 빌드 시점에 구글 폰트 파일을 내려받아서 프로젝트 자체 정적 자산으로 포함시켜요. 런타임엔 구글 서버로 요청을 안 보내니까 외부 요청 한 단계가 줄고, Next.js가 폰트 파일에 맞는 size-adjust 값을 알아서 계산해줘서 폰트가 늦게 로드돼도 텍스트 크기가 확 바뀌는 현상이 덜하더라고요.

Frontend Note: CDN에서 직접 스타일시트를 불러와요

<link rel="stylesheet" as="style" crossorigin
  href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />

이 사이트는 반대로 jsdelivr CDN에서 Pretendard 스타일시트를 바로 불러와요. 빌드 파이프라인에 폰트 파일을 안 넣어도 되고 CDN 캐시를 쓸 수 있다는 장점은 있는데, 처음 방문한 사용자한테는 CDN 요청이 렌더링 경로에 하나 더 끼어드는 셈이라 폰트가 늦게 도착하면 대체 폰트로 먼저 그려졌다가 바뀌는 순간이 생기더라고요.

둘 중 하나가 항상 정답은 아니었어요

자체 호스팅은 요청 수를 줄이고 레이아웃 시프트 계산까지 알아서 해주는데, 빌드 결과물이 커지고 빌드 파이프라인이 Next.js 기능에 묶여요. CDN 방식은 빌드가 단순하고 프레임워크에 안 얽매이는데, 외부 요청이랑 폰트 교체 시점을 직접 관리해야 하고요. 이 사이트는 Vite 기반 SPA라 애초에 next/font를 못 쓰는 제약도 있어서, 결국 두 선택은 각자 프레임워크 안에서 내린 결정이지 어느 쪽이 우월하다고 말하긴 어렵더라고요.