Next.js Font Optimization (Fonts) 정리

Next.js Docs – Getting Started: Font Optimization (Fonts) 페이지를 기반으로, App Router 환경에서의 폰트 사용 및 최적화 방식을 정리한 문서입니다.


1. 개요

Next.js는 next/font 모듈을 통해 폰트를 자동으로 최적화합니다.

  • 외부 네트워크 요청(예: Google Fonts CDN)을 제거하여 프라이버시와 성능을 개선합니다.
  • 어떤 폰트 파일이든 자동 self-hosting(직접 호스팅)을 지원합니다.
  • 폰트를 최적화된 방식으로 로드하여 레이아웃 시프트(Layout Shift) 없이 텍스트를 표시할 수 있습니다.

기본 사용 방식은 다음과 같습니다.

  1. next/font/google 또는 next/font/local 에서 폰트 함수를 import
  2. 해당 함수를 옵션과 함께 호출하여 폰트 인스턴스를 생성
  3. 생성된 인스턴스의 className적용하고 싶은 요소에 지정
// app/layout.tsx
import { Geist } from 'next/font/google'

const geist = Geist({
  subsets: ['latin'],
})

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={geist.className}>
      <body>{children}</body>
    </html>
  )
}
  • 폰트는 사용된 컴포넌트 범위(scope) 에만 적용됩니다.
  • 애플리케이션 전체에 공통 폰트를 적용하고 싶다면 Root Layout (app/layout.tsx) 에서 설정하는 것이 일반적입니다.

2. Google Fonts

2.1 자동 self-hosting

Next.js는 Google Fonts를 자동으로 self-hosting 합니다.

  • 선택한 Google Font가 빌드 시 정적 에셋으로 다운로드되어,
  • 실제 서비스 시에는 자체 도메인에서 폰트 파일을 제공합니다.
  • 사용자가 웹사이트에 접속할 때 브라우저는 Google 서버로 요청을 보내지 않습니다.

2.2 기본 사용 예시

// app/layout.tsx
import { Geist } from 'next/font/google'

const geist = Geist({
  subsets: ['latin'],
})

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={geist.className}>
      <body>{children}</body>
    </html>
  )
}
  • next/font/google에서 사용할 폰트를 import 합니다.
  • subsets 옵션에 ['latin'], ['latin', 'latin-ext'] 등 필요한 서브셋만 지정하면
    • 다운로드되는 폰트 파일 크기를 줄여 성능을 최적화할 수 있습니다.

2.3 Variable Font 권장

문서에서는 variable font(가변 폰트) 사용을 추천합니다.

  • 장점
    • 한 파일에 여러 굵기/스타일이 포함되므로 요청 수와 용량을 줄일 수 있음
    • 디자인적으로 더 유연하게 폰트 두께를 조절 가능
  • Google Fonts에서 variable font를 지원하는 패밀리를 선택하는 것이 좋습니다.

2.4 Variable Font를 사용하지 못할 경우

Variable Font를 사용할 수 없는 경우, 명시적으로 weight(굵기)를 지정해야 합니다.

// app/layout.tsx
import { Roboto } from 'next/font/google'

const roboto = Roboto({
  weight: '400',        // 또는 ['400', '700'] 처럼 배열도 가능
  subsets: ['latin'],
})

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={roboto.className}>
      <body>{children}</body>
    </html>
  )
}
  • weight'400', '700' 등의 문자열 또는 문자열 배열을 사용할 수 있습니다.
  • 필요 이상으로 많은 굵기를 포함할수록 폰트 파일 용량이 커지므로,
    • 프로젝트에서 실제로 사용하는 굵기만 선택하는 것이 좋습니다.

3. Local Fonts (로컬 폰트)

3.1 기본 사용

로컬 폰트를 사용하려면 next/font/local에서 default export를 import 한 뒤,
src 옵션에 로컬 폰트 파일 경로를 지정합니다.

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

const myFont = localFont({
  src: './my-font.woff2',
})

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={myFont.className}>
      <body>{children}</body>
    </html>
  )
}
  • 폰트 파일은 다음 위치에 둘 수 있습니다.
    • public 폴더 내부 (예: /public/fonts/my-font.woff2)
    • app 폴더 내부에 컴포넌트와 함께 위치(co-located)

src에 상대 경로를 사용할 때는 해당 파일이 위치한 곳을 기준으로 경로를 작성합니다.

3.2 여러 파일을 하나의 폰트 패밀리로 사용하기

하나의 폰트 패밀리에 대해 여러 굵기/스타일 파일을 함께 사용하고 싶다면,
src를 배열로 지정할 수 있습니다.

import localFont from 'next/font/local'

const roboto = localFont({
  src: [
    {
      path: './Roboto-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: './Roboto-Italic.woff2',
      weight: '400',
      style: 'italic',
    },
    {
      path: './Roboto-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
    {
      path: './Roboto-BoldItalic.woff2',
      weight: '700',
      style: 'italic',
    },
  ],
})
  • 각 항목은 다음 속성을 가집니다.
    • path: 폰트 파일 경로
    • weight: '400', '700' 등 폰트 굵기
    • style: 'normal' 또는 'italic'

이렇게 설정해 두면, Next.js가 한 폰트 패밀리를 기준으로
여러 굵기와 스타일을 자동으로 매핑하여 사용할 수 있도록 CSS를 생성합니다.


4. 폰트 적용 범위와 Root Layout

4.1 컴포넌트 스코프

next/font로 생성된 폰트는 해당 폰트를 사용하는 컴포넌트 범위에만 적용됩니다.

  • 예를 들어, 특정 섹션 컴포넌트에만 다른 폰트를 적용하고 싶다면,
    • 그 컴포넌트 파일에서 폰트를 import/생성하고,
    • 원하는 요소에만 className={myFont.className}을 적용하면 됩니다.
// app/(marketing)/hero.tsx
import localFont from 'next/font/local'

const displayFont = localFont({
  src: './Display-Title.woff2',
})

export function HeroSection() {
  return (
    <section>
      <h1 className={displayFont.className}>브랜드 메시지</h1>
      <p>본문은 기본 폰트 사용</p>
    </section>
  )
}

4.2 애플리케이션 전체 폰트 설정

앱 전체에 동일한 폰트를 적용하고 싶다면 Root Layout에 설정하는 것이 일반적입니다.

// app/layout.tsx
import { Geist } from 'next/font/google'

const geist = Geist({
  subsets: ['latin'],
})

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={geist.className}>
      <body>{children}</body>
    </html>
  )
}
  • <html>className={geist.className}을 지정하면,
    전체 문서에 해당 폰트가 적용됩니다.
  • 필요하다면 특정 영역만 다른 폰트로 덮어쓰는 것도 가능합니다.