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

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 글꼴 선언을 자바스크립트로 이동합니다. 함수의 반환 값은 구성요소 템플릿에서 활용할 수 있는 클래스 이름입니다. 이 기능을 사용 설정하려면 구성 객체에 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입니다. Serif 글꼴을 사용하려면 이 값을 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 dir로는 이 기능을 지원할 계획이 없습니다. 장기적으로는 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%;
}

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

: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. 글꼴 스택을 만들고 글꼴 모음 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));