카테고리 없음

웹 폰트 개념 및 Next.js + tailwind 에서 한글 웹폰트 최적화하기

소이소 2025. 11. 24. 09:29

 

서론

웹 폰트는 사용자 경험에 직접 영향을 미칩니다. 큰 파일 크기는 LCP(Largest Contentful Paint) 같은 Core Web Vitals 지표를 악화시킵니다. 특히 한글 폰트는 영문 폰트보다 문자 수가 많아 파일 크기가 큽니다. 따라서 적절한 폰트 파일을 import하여 사용자 경험을 향상시켜야합니다.

이 글에서는 브라우저 폰트에 대한 개념을 알아보고 Next.js + tailwind 프로젝트에 Pretendard 폰트를 적용하고 최적화한 경험을 다룹니다.

 

본론

1. 폰트 포맷의 진화: woff vs woff2

웹 폰트 포맷의 역사

웹 폰트는 TTF/OTF, EOT(IE 전용), WOFF, WOFF2로 진화했습니다

 

WOFF2는 가장 높은 압축률로 더 작은 파일 크기를 가져 빠른 로딩 속도를 가집니다. IE 브라우저 지원이 필요하지 않다면 WOFF2를 사용하는 것을 권장합니다. 

  포맷 압축률 지원 브라우저 권장 여부 Pretendard Bold 기준
파일 크기
TTF/OTF 압축 없음 대부분 지원 ❌ (웹에서 비권장) ~500KB
WOFF Gzip 압축 IE9+ 포함 전체 △ (레거시 브라우저용) ~342KB
WOFF2 Brotli 압축 모던 브라우저 (97%+) ✅ (권장) ~264KB

 

2. Variable Font vs Subset Font: 무엇을 선택할까?

Variable Font란?

Variable Font는 하나의 파일에 모든 굵기(weight)와 스타일을 포함하는 폰트입니다.

/* Variable Font 예시 */
@font-face {
  font-family: 'Pretendard Variable';
  src: url('/fonts/PretendardVariable.woff2') format('woff2-variations');
  font-weight: 1 999; /* 모든 굵기 지원 */
}

/* 사용 시 */
.text {
  font-weight: 450; /* 중간값도 사용 가능! */
}

 

Variable Font의 장단점:

👍 장점 👎 단점
  • 하나의 파일로 모든 굵기 지원 (100~900)
  • 중간값 사용 가능 (예: font-weight: 450)
  • HTTP 요청 1번만 필요
  • 관리가 간편
  • 파일 크기가 큼 (~1-2MB, 한글 기준)
  • 초기 로딩 시간 증가
  • 모든 굵기를 사용하지 않으면 파일 크기 낭비

 

Subset Font란?

Subset Font는 필요한 문자와 굵기만 포함한 최적화된 폰트입니다.

// Subset 전략 예시
한글 완성형: 11,172자 → 파일 크기 매우 큼 (1-2MB)
한글 상용 2,350자 + 영문/숫자 → 대폭 감소 (200-300KB)

 

Subset Font를 사용하면 파일 크기가 감소하여 더 빠른 초기 로딩을 할 수 있습니다.

따라서 특정 굵기만을 디자인에서 사용한다면 subset 한글 폰트 사용을 권장합니다.

3. 폰트가 웹 성능에 미치는 영향

개발자도구 Core Web Vitals 중 폰트와 관련된 지표:

 

1. LCP (Largest Contentful Paint)

  • 목표: 2.5 sec 이하
  • 큰 폰트 파일은 다운로드를 지연시켜 LCP를 증가시킵니다.
  • FOIT(Flash of Invisible Text)는 콘텐츠 렌더링을 지연시킵니다.

2. CLS (Cumulative Layout Shift)

  • 목표: 0.1 sec 이하
  • 폰트 로딩 전후 크기 차이는 레이아웃 이동을 유발합니다.
  • 대체 폰트와 메인 폰트의 크기 불일치가 원인입니다.

폰트 로딩 전략

브라우저의 폰트 로딩 방식의 종류는 다음과 같습니다.

 

font-display: auto;     /* 브라우저 기본값 (~3s FOIT) */
font-display: block;    /* FOIT, 최대 3초 대기 */
font-display: swap;     /* FOUT, 즉시 대체 폰트 표시 */
font-display: fallback; /* 짧은 대기 후 대체 폰트 */
font-display: optional; /* 네트워크 상태에 따라 결정 */

 

이 종류 중 font-display: swap를 사용하는 것을 권장합니다.

swap은 FOUT 라는 웹 폰트 로딩 방식을 사용합니다.

 

폰트가 렌더링되기 전까지 대체 폰트를 사용자게에 보여주어, 사용자에게 바로 텍스트를 보여줄 수 있으며 빈화면이 사용자에게 보여지는 과정을 줄입니다. 웹 폰트 로딩이 완료되면 폰트를 전환하게 되는데, 이 과정에서 글꼴의 자간, 높이 등 서식이 달라 웹 폰트 적용 전과 후에 레이아웃이 변경될 수 있습니다. 하지만 빠른 렌더링으로 FCP와 LCP 성능을 향상시킵니다..

  • FCP(First Contentful Paint): 브라우저가 페이지의 첫 번째 요소(일반적으로 텍스트)를 렌더링한 시점입니다. 사용자가 페이지를 요청한 후 FCP까지 걸리는 시간이 짧을수록 페이지는 빠르게 로드된 것으로 간주됩니다.
  • LCP(Largest Contentful Paint): 페이지에서 가장 큰 요소(일반적으로 이미지 또는 비디오)가 렌더링된 시점입니다. LCP 시간은 페이지의 로딩 시간과 사용자 경험에 큰 영향을 미칩니다.

실제 성능 비교

700(bold), 500(medium), 400(regular) 굵기만 사용할때 Variable 대신 subset을 선택한다면 다음과 같이 더 가벼운 파일을 빠르게 다운로드할 수 있습니다.

// Before: Variable Font
파일 크기: 2.1MB
다운로드 시간 (throttling 없음): 129ms

// After: Subset Font (3개 굵기)
파일 크기: 806KB (3개 합계)
다운로드 시간 (throttling 없음): 18ms, 18ms, 22ms

 

4. Next.js의 폰트 최적화: next/font

 

Next.js의 next/font 는 폰트를 내부에서 self-hosting하여 자동 preload 하는 기능을 통해 폰트 로딩을 최적화합니다:

 

layout.tsx에서 pretendard.variable로 --font-pretendard CSS 변수를 정의합니다.

// app/layout.tsx
import localFont from 'next/font/local';

const pretendard = localFont({
  src: [
    {
      path: '../fonts/Pretendard-Regular.subset.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: '../fonts/Pretendard-Medium.subset.woff2',
      weight: '500',
      style: 'normal',
    },
    {
      path: '../fonts/Pretendard-Bold.subset.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
  display: 'swap',
  variable: '--font-pretendard',
});

export default function RootLayout({ children }) {
  return (
    <html lang="ko">
      <body className={`${pretendard.variable}`}>
        {children}
      </body>
    </html>
  );
}

 

 

globals.css에서 body에서 정의한 CSS 변수를  font-family로 지정합니다.

body {
  font-family: var(--font-pretendard), sans-serif;
  @apply bg-background text-foreground;
}

5. Next.js + tailwind 프로젝트에 pretendard 폰트 적용하기

Next.js + tailwind 프로젝트에 폰트를 적용하는 과정을 정리하면 다음과 같습니다.

Step 1: 폰트 파일 준비

# 프로젝트 구조
src/
├── fonts/
│   ├── Pretendard-Regular.subset.woff2  # 400
│   ├── Pretendard-Medium.subset.woff2   # 500
│   └── Pretendard-Bold.subset.woff2     # 700
└── app/
    └── layout.tsx

Step 2: layout.tsx 설정

// src/app/layout.tsx
import type { Metadata } from 'next';
import localFont from 'next/font/local';
import './globals.css';

const pretendard = localFont({
  src: [
    {
      path: '../fonts/Pretendard-Regular.subset.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: '../fonts/Pretendard-Medium.subset.woff2',
      weight: '500',
      style: 'normal',
    },
    {
      path: '../fonts/Pretendard-Bold.subset.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
  display: 'swap',
  variable: '--font-pretendard',
});

export const metadata: Metadata = {
  title: 'My App',
  description: 'Optimized with subset fonts',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ko">
      <body className={`${pretendard.variable} font-pretendard antialiased`}>
        {children}
      </body>
    </html>
  );
}

Step 3: Tailwind CSS 설정

/* globals.css  ( tailwind v4 ) */

@layer base {
  body {
    font-family: var(--font-pretendard), sans-serif; // sans-serif 는 대체 폰트
  }
}

 

 

결론

어떤 프로젝트를 진행하든 가장 기본 세팅요소 중 하나는 폰트인 것 같습니다. 이 글을 작성하며 파편적으로 분산되어있던 웹폰트에 대해 정리할 수 있었으며 실제 사이드프로젝트에서 디자인 시스템 구축을 위한 폰트 세팅을 하면서 활용해볼 수 있었습니다. 디자인 시스템을 구축한 과정도 공유하겠습니다.

 

🙇‍♂️ 참고문헌

Next.js Font Optimization 공식 문서

Web Font Best Practices (web.dev)