글꼴 대체를 위한 프레임워크 도구

Janicklas Ralph James
Janicklas Ralph James

font-display: swap으로 글꼴을 로드하는 사이트는 웹 글꼴이 로드되고 대체 글꼴로 전환될 때 레이아웃 변경 (CLS)이 발생하는 경우가 많습니다.

대체 글꼴의 크기를 기본 글꼴의 크기와 일치하도록 조정하여 CLS를 방지할 수 있습니다. @font-face 규칙의 size-adjust, ascent-override, descent-override, line-gap-override와 같은 속성을 사용하면 개발자가 글꼴 표시 방식을 더 세부적으로 제어할 수 있도록 대체 글꼴의 측정항목을 재정의하는 데 도움이 됩니다. font-fallbacks 및 재정의 속성에 관한 자세한 내용은 이 게시물을 참고하세요. 이 데모에서 이 기법의 작동하는 구현을 확인할 수도 있습니다.

이 도움말에서는 Next.js 및 Nuxt.js 프레임워크에서 글꼴 크기 조정을 구현하여 대체 글꼴 CSS를 생성하고 CLS를 줄이는 방법을 살펴봅니다. 또한 Fontaine 및 Capsize와 같은 교차 도구를 사용하여 대체 글꼴을 생성하는 방법도 보여줍니다.

배경

font-display: swap은 일반적으로 FOIT (보이지 않는 텍스트의 플래시)를 방지하고 화면에 콘텐츠를 더 빠르게 표시하는 데 사용됩니다. swap 값은 브라우저에 글꼴을 사용하는 텍스트를 시스템 글꼴을 사용하여 즉시 표시하고 맞춤 글꼴이 준비된 경우에만 시스템 글꼴을 대체해야 한다고 알려줍니다.

swap의 가장 큰 문제는 두 글꼴의 글자 크기 차이로 인해 화면 콘텐츠가 움직이는 불편한 효과입니다. 이로 인해 특히 텍스트가 많은 웹사이트의 CLS 점수가 낮아집니다.

다음 이미지는 이 문제의 예를 보여줍니다. 첫 번째 이미지는 대체 글꼴의 크기를 조정하지 않고 font-display: swap를 사용합니다. 두 번째는 CSS @font-face 규칙을 사용하여 크기를 조정하면 로드 환경이 개선되는 방식을 보여줍니다.

글꼴 크기 조정 안함

body {
  font-family: Inter, serif;
}
글꼴과 크기가 갑자기 변경되어 불편한 느낌을 주는 텍스트

글꼴 크기 조정 후

body {
  font-family: Inter, fallback-inter, serif;
  }

@font-face {
  font-family: "fallback-inter";
  ascent-override: 90.20%;
  descent-override: 22.48%;
  line-gap-override: 0.00%;
  size-adjust: 107.40%;
  src: local("Arial");
}
다른 글꼴로 원활하게 전환되는 텍스트

대체 글꼴의 크기를 조정하는 것이 글꼴 로드 레이아웃 전환을 방지하는 효과적인 전략이 될 수 있지만, 글꼴 대체에 관한 이 게시물에 설명된 대로 로직을 처음부터 구현하는 것은 까다로울 수 있습니다. 다행히 앱을 개발하는 동안 이를 더 쉽게 할 수 있는 여러 도구 옵션이 이미 있습니다.

Next.js로 글꼴 대체를 최적화하는 방법

Next.js는 대체 글꼴 최적화를 사용 설정하는 기본 제공 방법을 제공합니다. 이 기능은 @next/font 구성요소를 사용하여 글꼴을 로드할 때 기본적으로 사용 설정됩니다.

@next/font 구성요소는 Next.js 버전 13에서 도입되었습니다. 이 구성요소는 Google Fonts 또는 맞춤 글꼴을 페이지로 가져오는 API를 제공하며 글꼴 파일의 자동 셀프 호스팅을 기본적으로 포함합니다.

대체 글꼴 측정항목을 사용하면 자동으로 계산되어 CSS 파일에 삽입됩니다.

​​예를 들어 Roboto 글꼴을 사용하는 경우 일반적으로 CSS에서 다음과 같이 정의합니다.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

body {
  font-family: Roboto;
}

next/font로 이전하려면 다음 단계를 따르세요.

  1. 'next/font'에서 'Roboto' 함수를 가져와 Roboto 글꼴 선언을 JavaScript로 이동합니다. 함수의 반환 값은 구성요소 템플릿에서 활용할 수 있는 클래스 이름입니다. 이 기능을 사용 설정하려면 구성 객체에 display: swap를 추가해야 합니다.

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. 구성요소에서 생성된 클래스 이름(javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); })을 사용합니다.

adjustFontFallback 구성 옵션:

@next/font/google: 누적 레이아웃 전환을 줄이기 위해 자동 대체 글꼴을 사용해야 하는지 여부를 설정하는 불리언 값입니다. 기본값은 true입니다. Next.js는 글꼴 유형 (각각 서체와 산세리프)에 따라 대체 글꼴을 Arial 또는 Times New Roman로 자동 설정합니다.

@next/font/local의 경우: 누적 레이아웃 전환을 줄이기 위해 자동 대체 글꼴을 사용해야 하는지 여부를 설정하는 문자열 또는 불리언 false 값입니다. 가능한 값은 Arial, Times New Roman 또는 false입니다. 기본값은 Arial입니다. 세리프 글꼴을 사용하려면 이 값을 Times New Roman로 설정하는 것이 좋습니다.

Google Fonts의 다른 옵션

next/font 구성요소를 사용할 수 없는 경우 optimizeFonts 플래그를 통해 Google Fonts에서 이 기능을 사용하는 다른 방법이 있습니다. Next.js에는 optimizeFonts 기능이 기본적으로 사용 설정되어 있습니다. 이 기능은 HTML 응답에 Google Font CSS를 인라인 처리합니다. 또한 다음 스니펫과 같이 next.config.js에서 experimental.adjustFontFallbacksWithSizeAdjust 플래그를 설정하여 글꼴 대체 조정 기능을 사용 설정할 수 있습니다.

// In next.config.js
module.exports = {
 experimental: {
   adjustFontFallbacksWithSizeAdjust: true,
 },
}

참고: 새로 도입된 app 디렉터리로 이 기능을 지원할 계획은 없습니다. 장기적으로는 next/font를 사용하는 것이 이상적입니다.

Nuxt로 글꼴 대체를 조정하는 방법

@nuxtjs/fontaine는 대체 글꼴 측정항목 값을 자동으로 계산하고 대체 @font-face CSS를 생성하는 Nuxt.js 프레임워크용 모듈입니다.

모듈 구성에 @nuxtjs/fontaine를 추가하여 모듈을 사용 설정합니다.

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

Google Fonts를 사용하거나 글꼴에 대한 @font-face 선언이 없는 경우 추가 옵션으로 선언할 수 있습니다.

대부분의 경우 모듈은 CSS에서 @font-face 규칙을 읽고 font-family, 대체 글꼴 모음, 디스플레이 유형과 같은 세부정보를 자동으로 추론할 수 있습니다.

모듈에서 검색할 수 없는 위치에 글꼴이 정의된 경우 다음 코드 스니펫과 같이 측정항목 정보를 전달할 수 있습니다.

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
  fontMetrics: {
  fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})

이 모듈은 CSS를 자동으로 검사하여 @font-face 선언을 읽고 대체 @font-face 규칙을 생성합니다.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}
/* This will be generated. */
@font-face {
  font-family: 'Roboto override';
  src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'),
    local('Arial'), local('Noto Sans');
  ascent-override: 92.7734375%;
  descent-override: 24.4140625%;
  line-gap-override: 0%;
}

이제 다음 예와 같이 CSS에서 Roboto override를 대체 글꼴로 사용할 수 있습니다.

:root {
  font-family: 'Roboto';
  /* This becomes */
  font-family: 'Roboto', 'Roboto override';
}

CSS 직접 생성

독립형 라이브러리를 사용하면 대체 글꼴 크기 조정을 위한 CSS를 생성하는 데도 도움이 됩니다.

Fontaine 라이브러리 사용

Nuxt 또는 Next.js를 사용하지 않는 경우 Fontaine을 사용할 수 있습니다. Fontaine는 @nuxtjs/fontaine을 지원하는 기본 라이브러리입니다. 이 라이브러리를 프로젝트에서 사용하여 Vite 또는 Webpack 플러그인을 사용하여 대체 글꼴 CSS를 자동으로 삽입할 수 있습니다.

CSS 파일에 Roboto 글꼴이 정의되어 있다고 가정해 보겠습니다.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

Fontaine은 빌드 체인에 쉽게 연결할 수 있는 ViteWebpack 변환기를 제공합니다. 다음 JavaScript와 같이 플러그인을 사용 설정하세요.

import { FontaineTransform } from 'fontaine'

const options = {
  fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
  // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
  resolvePath: (id) => 'file:///path/to/public/dir' + id,
  // overrideName: (originalName) => `${name} override`
  // sourcemap: false
}

Vite를 사용하는 경우 다음과 같이 플러그인을 추가합니다. javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }

Webpack을 사용하는 경우 다음과 같이 사용 설정합니다.

// Webpack
export default {
  plugins: [FontaineTransform.webpack(options)]
}

모듈이 파일을 자동으로 스캔하여 @font-face 규칙을 수정합니다. css @font-face { font-family: 'Roboto'; font-display: swap; src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff'); font-weight: 700; } /* This will be generated. */ @font-face { font-family: 'Roboto override'; src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'), local('Arial'), local('Noto Sans'); ascent-override: 92.7734375%; descent-override: 24.4140625%; line-gap-override: 0%; }

이제 CSS에서 Roboto override을 대체 글꼴로 사용할 수 있습니다. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

Capsize 라이브러리 사용

Next.js, Nuxt, Webpack 또는 Vite를 사용하지 않는 경우 Capsize 라이브러리를 사용하여 대체 CSS를 생성하는 방법도 있습니다.

createFontStack API

이 API는 글꼴 스택 (font-family 속성)을 지정할 때와 동일한 순서로 글꼴 측정항목 배열을 허용하는 createFontStack이라는 @capsize/core 패키지의 일부입니다.

Capsize 사용에 관한 문서는 여기에서 참고하세요.

다음 예를 생각해 보세요. 원하는 웹 글꼴은 Lobster이고, Helvetica Neue, Arial 순으로 대체됩니다. CSS의 경우 font-family: Lobster, 'Helvetica Neue', Arial입니다.

  1. 코어 패키지에서 createFontStack을 가져옵니다.

    import { createFontStack } from '@capsizecss/core';
    
  2. 원하는 각 글꼴의 글꼴 측정항목을 가져옵니다 (위의 글꼴 측정항목 참고). javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. font-family CSS 속성을 사용할 때와 동일한 순서를 사용하여 측정항목을 배열로 전달하여 글꼴 스택을 만듭니다. javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

이는 다음을 반환합니다.

{
  fontFamily: Lobster, 'Lobster Fallback: Helvetica Neue', 'Lobster Fallback: Arial',
  fontFaces: [
    {
      '@font-face' {
      'font-family': '"Lobster Fallback: Helvetica Neue"';
      src: local('Helvetica Neue');
      'ascent-override': '115.1741%';
      'descent-override': '28.7935%';
      'size-adjust': '86.8251%';
      }
     '@font-face' {
       'font-family': '"Lobster Fallback: Arial"';
       src: local('Arial');
       'ascent-override': 113.5679%;
       'descent-override': 28.392%;
       'size-adjust': 88.053%;
     }
   }
 ]
}

CSS에 fontFamily 및 fontFaces 코드를 추가해야 합니다. 다음 코드는 CSS 스타일 시트 또는 <style> 블록 내에서 이를 구현하는 방법을 보여줍니다.

<style type="text/css">
  .heading {
    font-family: 
  }

  
</style>

그러면 다음과 같은 CSS가 생성됩니다.

.heading {
  font-family: Lobster, 'Lobster Fallback: Helvetica Neue',
    'Lobster Fallback: Arial';
}

@font-face {
  font-family: 'Lobster Fallback: Helvetica Neue';
  src: local('Helvetica Neue');
  ascent-override: 115.1741%;
  descent-override: 28.7935%;
  size-adjust: 86.8251%;
}
@font-face {
  font-family: 'Lobster Fallback: Arial';
  src: local('Arial');
  ascent-override: 113.5679%;
  descent-override: 28.392%;
  size-adjust: 88.053%;
}

@capsize/metrics 패키지를 사용하여 재정의 값을 계산하고 CSS에 직접 적용할 수도 있습니다.

const fontMetrics = require(`@capsizecss/metrics/inter`);
const fallbackFontMetrics = require(`@capsizecss/metrics/arial`);
const mainFontAvgWidth = fontMetrics.xAvgWidth / fontMetrics.unitsPerEm;
const fallbackFontAvgWidth = fallbackFontMetrics.xAvgWidth / fallbackFontMetrics.unitsPerEm;
let sizeAdjust = mainFontAvgWidth / fallbackFontAvgWidth;
let ascent = fontMetrics.ascent / (unitsPerEm * fontMetrics.sizeAdjust));
let descent = fontMetrics.descent / (unitsPerEm * fontMetrics.sizeAdjust));
let lineGap = fontMetrics.lineGap / (unitsPerEm * fontMetrics.sizeAdjust));