새로운 웹뷰 프로젝트를 셋업하면서 “어? 폰트가 뭔가 다른데?”라는 의문에서 시작된 긴 여정의 기록입니다. Next.js에서는 완벽했던 폰트가 Vite 기반 프로젝트로 넘어오니 미묘하게 달라 보였고, 이를 해결하는 과정에서 브라우저의 폰트 렌더링, CSS 최적화, 그리고 UI 라이브러리의 숨겨진 동작까지 깊이 이해할 수 있었습니다.


🔍 문제 발견: “왜 폰트가 다르지?”

증상

%E1%84%91%E1%85%B5%E1%84%80%E1%85%B3%E1%84%86%E1%85%A1.png

  • 피그마

%E1%84%8C%E1%85%A5%E1%86%AB.png

  • 폰트 적용 전

%E1%84%92%E1%85%AE.png

  • 폰트 적용 후

피그마 디자인과 실제 화면을 비교해보니 미묘한 차이가 있었습니다.

특히 이상했던 점들:

  • Next.js 프로젝트에서는 정상적으로 보였던 폰트
  • 개발 서버에 폰트를 적용하지 않았는데도 “프리텐다드”처럼 보임
  • 로컬 폰트를 적용하니 오히려 더 두꺼워짐
  • input placeholder만 다른 폰트로 보임

🕵️ 원인 분석 1: 유령 폰트의 정체

시스템 폰트가 적용되고 있었다

첫 번째 반전. 우리가 의도하지 않은 폰트가 적용되고 있었습니다.

케이스 1: Pretendard가 설치된 컴퓨터

/* 브라우저가 시스템 폰트를 찾아서 적용 */
font-family: "Pretendard", sans-serif;
/* → 시스템의 Pretendard 사용 ✅ */

케이스 2: Pretendard가 없는 컴퓨터

font-family: "Pretendard", sans-serif;
/* → sans-serif 폴백 폰트 사용 (애플 SD 산돌고딕 Neo 등) */
/* 이게 Pretendard와 비슷해서 착각할 수 있었습니다 */

🕵️ 원인 분석 2: Medium 파일의 함정

로컬 폰트를 적용했더니 오히려 문제가 더 커졌습니다.

// 기존 코드
export const Pretendard = localFont({
  src: "/fonts/Pretendard-Medium.woff2",
  variable: "--font-pretendard",
});

Medium 파일의 한계

%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2025-10-10_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_3.57.57.png

기존에 설치된 폰트는 미디움 형태였습니다

variable이 아닌 미디움인 이유는 경량화와 디자인시스템상 사용하지 않아서의 영향이 있는것 같았습니다.

Pretendard-Medium.woff2가 지원하는 범위는 아래와 같습니다.

  • font-weight: 600 ~ 900: 정상 작동
  • font-weight: 100 ~ 500: 미지원

발생한 문제들

1. 얇은 폰트(100-500)가 적용되지 않음

2. 굵은 폰트(600-900)가 과도하게 두꺼워짐

브라우저는 요청한 굵기의 폰트 파일이 없으면, 기존 폰트를 “억지로 굵게” 만듭니다. 이 과정에서 폰트가 뭉개지고 디자이너가 의도한 것과 달라집니다.


🕵️ 원인 분석 3: Chakra UI의 숨겨진 설정

Next.js에서는 정상이었는데 왜 Vite에서만 다를까? 범인은 Chakra UI였습니다.

font-smoothing의 영향

Chakra UI 내부 코드를 보니:

https://github.com/chakra-ui/chakra-ui/blob/610180eec970ca63c092796f0be3bbd4cb794f72/packages/react/src/styled-system/preflight.ts#L151

const preflightCss: Record<string, CssProperties> = {
    [scope || "html"]: {
      lineHeight: 1.5,
      "--font-fallback":
        "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
      WebkitTextSizeAdjust: "100%",
      WebkitFontSmoothing: "antialiased",
      MozOsxFontSmoothing: "grayscale",
      ...
      }

이 설정이 폰트를 부드럽게 렌더링하면서 미묘한 차이를 만들었습니다.

우리 프로젝트는 웹뷰의 경우 테일윈드를 사용하게 되었고 기존 웹 프로젝트는 차크라 ui를 사용하고 있다보니 나온 차이점이었습니다.


Heading 컴포넌트의 독자적인 폰트 설정

더 큰 문제는 이거였습니다:

https://github.com/chakra-ui/chakra-ui/blob/610180eec970ca63c092796f0be3bbd4cb794f72/packages/react/src/theme/recipes/heading.ts

// Chakra UI의 Heading 컴포넌트

export const headingRecipe = defineRecipe({
  className: "chakra-heading",
  base: {
    fontFamily: "heading",
    fontWeight: "semibold",
  },
  ...
  }

전역 CSS로 body { font-family: "Pretendard" }를 설정해도, Chakra의 <Heading> 컴포넌트는 무시합니다!

/* Heading에는 적용 안 됨 */
body {
  font-family: "Pretendard";
}

/* ✅ Chakra Config에서 heading에 영억도 설정해야 했습니다 */

%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2025-10-10_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_5.11.14.png

다른 폰트로 테스트 해본 결과 해딩을 사용한 폰트는 모두 적용이 안되었었습니다.


💡 Variable Font

모든 문제의 근본 원인은 고정 웨이트 폰트 파일과 차크라 ui로 인한 설정 미스입니다.

Variable Font

하나의 파일에 모든 굵기(100-900)를 담은 폰트입니다.

💡 Chakra UI 올바르게 설정하기

전역 CSS만으로는 부족합니다. Chakra UI는 자체 테마 시스템이 있습니다.

올바른 설정 방법

// chakra.config.ts
import { createSystem, defaultConfig } from "@chakra-ui/react";

const config = createSystem({
  theme: {
    tokens: {
      fonts: {
        body: "Pretendard",      // 일반 텍스트
        heading: "Pretendard",   // 제목 (Heading 컴포넌트)
        mono: "monospace",       // 코드
      }
    }
  }
});

export default config;

🎬 마치며

“폰트 하나 바꾸는 게 뭐가 어려워?”라고 생각했던 과거의 나를 되돌아봅니다. 이번 경험을 통해 브라우저의 폰트 렌더링 메커니즘, UI 라이브러리의 내부 동작, 그리고 웹 최적화에 대해 깊이 배울 수 있었습니다.

Variable Font는 단순히 파일 크기를 줄이는 것 이상의 가치가 있습니다. 디자인 일관성, 성능 최적화, 그리고 개발 편의성까지 모두 개선됩니다.

혹시 여러분도 “왜 폰트가 다르지?”라는 의문을 가지고 계신다면, 이 글이 도움이 되길 바랍니다.

Happy coding! 🚀


📚 참고 자료