안동민 개발노트

안동민 개발노트

이미지 최적화폰트 최적화코드 분할 및 지연 로딩메모이제이션 및 리렌더링 최적화
본문 시작
  1. 홈
  2. 문서
  3. Next.js
  4. 12장 : 성능 최적화
  5. 폰트 최적화
  1. Next.js
  2. 폰트 최적화

폰트 최적화

next/font로 Google·로컬 폰트를 자체 호스팅하고 서브셋·가변 폰트·fallback 설정으로 표시 지연을 줄입니다.

웹 폰트는 사용하는 글꼴·문자 범위·굵기에 따라 전송량이 커질 수 있는 자원입니다.

그래서 폰트 로딩 방식은 페이지 성능에 직접적인 영향을 줍니다.

폰트 파일이 크거나 로딩이 비효율적이면 텍스트 표시 지연, FOIT(Flash Of Invisible Text), FOUT(Flash Of Unstyled Text) 같은 문제가 발생해 사용자 경험을 해칠 수 있습니다.

Next.js는 이런 문제를 줄이기 위한 폰트 최적화 기능을 내장하고 있습니다.

이 절에서는 폰트 최적화의 중요성, Next.js의 next/font 모듈 사용법, 그리고 폰트 로딩 성능을 개선하기 위한 다양한 전략들을 알아보겠습니다.


폰트 최적화의 중요성

  • 성능 저하: 웹 폰트 파일은 용량이 크기 때문에 다운로드 시간이 길어지면 페이지 렌더링을 지연시킬 수 있습니다. 이는 특히 모바일 환경이나 네트워크 속도가 느린 환경에서 더욱 두드러집니다.
  • CLS(Cumulative Layout Shift) 발생: 폰트 파일 로딩이 완료되기 전에 대체 폰트(fallback font)로 텍스트가 렌더링되다가 폰트가 로드된 후 레이아웃이 변경될 때 CLS가 발생합니다. 이는 사용자의 시각적 안정성을 해치고 Core Web Vitals 점수에 부정적인 영향을 미칩니다.
  • FOIT/FOUT 현상
    • FOIT (Flash Of Invisible Text): 폰트가 로딩될 때까지 텍스트가 전혀 보이지 않는 현상입니다. 사용자에게 빈 화면을 보여주어 답답함을 유발합니다.
    • FOUT (Flash Of Unstyled Text): 폰트가 로딩될 때까지 시스템 기본 폰트나 대체 폰트로 텍스트가 표시되다가, 로딩이 완료되면 웹 폰트로 교체되면서 글꼴이 갑자기 바뀌는 현상입니다.

폰트 최적화는 이러한 문제들을 해결하여 웹 페이지의 시각적 안정성과 로딩 성능을 향상시키는 데 기여합니다.


next/font 모듈 사용하기

next/font를 사용할 때는 폰트 소스, 서브셋, display, 적용 위치를 함께 정해야 합니다.

Next.js 13부터 도입된 next/font는 Google Fonts 및 로컬 폰트를 빌드 과정에서 최적화하는 기능입니다.

이 모듈은 폰트 파일 다운로드 방식, font-display, 폰트 메트릭스 조정을 통해 FOIT/FOUT와 CLS 문제를 줄입니다.

Google 폰트 파일은 빌드 과정에서 준비해 앱과 함께 자체 호스팅합니다. 방문자의 브라우저가 Google Fonts 서버에 직접 요청하는 방식은 아니지만, 빌드 환경은 해당 파일을 가져올 수 있어야 합니다. 실제 로딩 속도는 파일 크기·캐시·네트워크에 따라 달라집니다.

Google Fonts 최적화

Google Fonts는 웹에서 가장 널리 사용되는 폰트 서비스 중 하나입니다.

next/font/google을 사용하면 Google Fonts를 빠르고 효율적으로 불러올 수 있습니다.

실습: Google Fonts 적용하기

폰트 불러오기: src/app/layout.tsx (루트 레이아웃) 또는 개별 페이지/컴포넌트 파일에서 next/font/google을 임포트하고 사용할 폰트를 설정합니다.

src/app/layout.tsx
import './globals.css';
import { Inter, Noto_Sans_KR } from 'next/font/google'; // 사용할 폰트 임포트

// 1. Inter 폰트 설정 (영문 기본 폰트)
const inter = Inter({
  subsets: ['latin'], // 미리 로드할 서브셋 지정
  display: 'swap',   // 대체 폰트를 먼저 표시한 뒤 웹 폰트로 교체
  variable: '--font-inter', // CSS 변수로 사용하기 위한 이름 지정
});

// 2. Noto Sans KR 폰트 설정 (한글 폰트)
// weight: 사용할 폰트 두께 지정 (용량 최적화)
// display: 'swap' (FOUT 방식)
// variable: CSS 변수 이름 지정
const notoSansKr = Noto_Sans_KR({
  subsets: ['latin'], // latin 서브셋을 preload하며 한글 글리프 제거 설정이 아님
  weight: ['100', '400', '700', '900'], // 사용할 두께만 지정하여 용량 최적화
  display: 'swap',
  variable: '--font-noto-sans-kr',
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    // 3. html 태그에 CSS 변수 클래스 적용
    // classNames 라이브러리 등을 사용하여 조건부 클래스 적용 가능
    <html lang="ko" className={`${inter.variable} ${notoSansKr.variable}`}>
      <body>{children}</body>
    </html>
  );
}

CSS에서 폰트 사용: src/app/globals.css 파일에서 위에서 설정한 CSS 변수를 사용하여 폰트를 적용합니다.

src/app/globals.css
html, body {
  padding: 0;
  margin: 0;
  font-family: var(--font-noto-sans-kr), var(--font-inter), sans-serif;
  /* 한글 폰트를 먼저 지정하여 우선 순위를 줍니다. */
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-noto-sans-kr), var(--font-inter), sans-serif;
}

/* 특정 요소에 Inter 폰트 적용 예시 */
.english-only {
  font-family: var(--font-inter), sans-serif;
}

/* 폰트 두께 사용 예시 */
.font-thin {
  font-weight: 100;
}
.font-bold {
  font-weight: 700;
}
next/font/google의 주요 옵션
  • subsets: preload할 서브셋을 지정합니다. 지원 이름은 폰트마다 다르며, 이 예제의 Noto Sans KR에 임의의 korean 값을 넣지 않습니다. ['latin']은 한글 글리프를 삭제한다는 뜻이 아닙니다.
  • weight: 사용할 폰트 두께를 지정합니다 (예: ['400', '700']). 모든 두께를 로드할 필요 없이 필요한 두께만 로드하여 용량을 최적화합니다.
  • display: 폰트가 늦게 준비될 때 텍스트 표시와 교체 방식을 정합니다. 기본값은 swap이며, 아래에서 정책을 비교합니다.
  • variable: 폰트의 CSS 변수 이름을 지정합니다. 이를 통해 CSS 파일에서 var(--font-변수명) 형태로 폰트를 쉽게 적용할 수 있습니다.
  • preload: 폰트를 미리 로드할지 정하며 기본값은 true입니다. root layout에서 사용하면 전체 경로에, 특정 layout이나 page에서 사용하면 해당 범위에 적용됩니다.
폰트 도착이 늦었을 때의 표시 정책

아직 폰트가 준비되지 않았을 때의 동작입니다. block과 swap의 기간은 브라우저 정책에 따라 달라집니다.

폰트 도착이 늦었을 때의 표시 정책
정책텍스트를 먼저 표시하는 방식늦게 도착한 폰트
block짧은 block 기간에는 글자를 숨기고, 이후 대체 폰트를 표시합니다.swap 기간에는 웹 폰트로 교체합니다.
swap매우 짧은 block 기간 뒤 대체 폰트를 표시합니다.나중에 도착해도 교체할 수 있어 FOUT가 남을 수 있습니다.
fallback매우 짧은 block 기간 뒤 대체 폰트를 표시합니다.짧은 swap 기간 안에 도착한 경우에만 교체합니다.
optional첫 표시까지 준비되지 않으면 대체 폰트를 사용합니다.대체 폰트로 표시한 뒤에는 해당 페이지에서 늦게 교체하지 않습니다.
block
텍스트를 먼저 표시하는 방식: 짧은 block 기간에는 글자를 숨기고, 이후 대체 폰트를 표시합니다.
늦게 도착한 폰트: swap 기간에는 웹 폰트로 교체합니다.
swap
텍스트를 먼저 표시하는 방식: 매우 짧은 block 기간 뒤 대체 폰트를 표시합니다.
늦게 도착한 폰트: 나중에 도착해도 교체할 수 있어 FOUT가 남을 수 있습니다.
fallback
텍스트를 먼저 표시하는 방식: 매우 짧은 block 기간 뒤 대체 폰트를 표시합니다.
늦게 도착한 폰트: 짧은 swap 기간 안에 도착한 경우에만 교체합니다.
optional
텍스트를 먼저 표시하는 방식: 첫 표시까지 준비되지 않으면 대체 폰트를 사용합니다.
늦게 도착한 폰트: 대체 폰트로 표시한 뒤에는 해당 페이지에서 늦게 교체하지 않습니다.

CSS 변수 클래스는 이름을 등록하고, 실제 글꼴 적용은 font-family가 담당합니다. 위 CSS는 Noto Sans KR을 먼저 시도하며 Inter가 모든 영문에 자동 적용되는 것은 아닙니다. 영문 전용 요소에는 .english-only를 적용합니다.

로컬 폰트 최적화

직접 호스팅하는 폰트 파일도 next/font/local을 사용하여 최적화할 수 있습니다.

실습: 로컬 폰트 적용하기

폰트 파일 준비: 프로젝트 내에 폰트 파일을 저장할 디렉토리를 생성합니다 (예: src/fonts).

아래 코드에 맞춰 my-custom-font-thin.woff2, my-custom-font-regular.woff2, my-custom-font-bold.woff2를 준비합니다. path는 폰트를 선언한 파일을 기준으로 해석됩니다.

WOFF2 형식은 가장 최신이고 압축 효율이 좋은 형식으로 권장됩니다.

폰트 불러오기: next/font/local을 사용하여 폰트 파일을 불러옵니다.

src/app/layout.tsx (또는 폰트를 사용할 다른 파일)
import './globals.css';
import localFont from 'next/font/local'; // 로컬 폰트 임포트
import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'swap' });

// 1. 로컬 폰트 설정
const myCustomFont = localFont({
  src: [
    // 여러 폰트 두께 및 스타일을 지정할 수 있습니다.
    {
      path: '../fonts/my-custom-font-thin.woff2',
      weight: '100',
      style: 'normal',
    },
    {
      path: '../fonts/my-custom-font-regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: '../fonts/my-custom-font-bold.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
  display: 'swap',
  variable: '--font-my-custom',
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    // 2. html 태그에 CSS 변수 클래스 적용
    <html lang="ko" className={`${inter.variable} ${myCustomFont.variable}`}>
      <body>{children}</body>
    </html>
  );
}
CSS에서 폰트 사용
src/app/globals.css
html, body {
  padding: 0;
  margin: 0;
  font-family: var(--font-my-custom), var(--font-inter), sans-serif;
}
next/font/local의 주요 옵션
  • src: 폰트 파일의 경로 배열. 각 객체는 path, weight, style을 포함할 수 있습니다.
  • display, preload, variable 등은 Google Fonts와 동일하게 작동합니다.

폰트 최적화를 위한 추가 전략

next/font를 사용하는 것 외에도 폰트 로딩 성능을 더욱 개선할 수 있는 몇 가지 전략이 있습니다.

  • 필요한 폰트만 로드: 모든 폰트 두께(thin, regular, bold, black 등)와 모든 스타일(normal, italic)을 한꺼번에 로드하면 폰트 파일 용량이 크게 늘어납니다. 실제로 사용되는 두께와 스타일만 선택하여 로드하세요. (weight·style과 preload할 subsets를 구분하여 선택)
  • 폰트 형식 선택
    • WOFF2: 현재 가장 효율적인 웹 폰트 형식입니다. 최신 브라우저에서 널리 지원합니다. 압축률은 글꼴과 원본에 따라 달라집니다.
    • WOFF: WOFF2를 지원하지 않는 구형 브라우저를 위한 대체 형식으로 사용할 수 있습니다.
    • TrueType (TTF), OpenType (OTF): 원본·용도에 따라 사용할 수 있지만 웹 배포에는 WOFF2의 전송량과 비교해 선택합니다.
  • 표시 정책 확인: 위 정책표를 기준으로 display를 정하고 대체 폰트와의 크기 차이를 확인합니다. 메트릭 조정은 레이아웃 이동을 줄이지만 모든 이동을 없애는 보장은 아닙니다.
  • font-loading API (고급): JavaScript의 Font Loading API를 사용하여 폰트 로딩 상태를 직접 제어하고, 폰트 로드 완료 후 특정 동작을 수행할 수 있습니다. 이는 더 세밀한 제어가 필요할 때 사용됩니다.
  • 폰트 서브셋팅 (Subsetting): 사용하지 않는 언어 글리프나 문자를 제거하여 폰트 파일 크기를 줄이는 기술입니다. 한글 폰트의 경우 모든 문자를 포함하면 용량이 매우 커지므로, 특정 목적(예: 웹사이트 제목)으로만 사용되는 폰트라면 서브셋팅을 고려할 수 있습니다. (폰트 제공업체 또는 도구를 통해 가능)
  • CDN 활용: 폰트 파일을 CDN(Content Delivery Network)에 호스팅하면 사용자에게 지리적으로 가까운 서버에서 폰트를 제공하여 로딩 속도를 향상시킬 수 있습니다.

폰트 최적화는 웹 성능 최적화의 중요한 부분이며, next/font와 같은 Next.js의 내장 도구를 활용하면 개발자가 직접 복잡한 설정을 할 필요 없이 빠르고 안정적인 폰트 로딩을 구현할 수 있습니다.

이미지 최적화

이전 페이지

코드 분할 및 지연 로딩

다음 페이지

이 페이지의 목차

폰트 최적화의 중요성next/font 모듈 사용하기Google Fonts 최적화로컬 폰트 최적화폰트 최적화를 위한 추가 전략