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

자니클라스 랄프 제임스
재니클라스 랄프 제임스

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

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

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

배경

font-display: swap은 일반적으로 FOIT (Flash of invisible text)를 방지하고 화면에 콘텐츠를 더 빠르게 표시하는 데 사용됩니다. 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;
}

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

  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는 글꼴 유형 (각각 Serif와 Sans Serif)에 따라 대체 글꼴을 ArialTimes New Roman로 자동 설정합니다.

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

Google 글꼴의 또 다른 옵션은

next/font 구성요소를 사용할 수 없다면 Google Fonts에서 이 기능을 사용하는 또 다른 방법은 optimizeFonts 플래그를 사용하는 것입니다. 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 규칙을 읽고 글꼴 모음, 대체 글꼴 모음, 디스플레이 유형과 같은 세부정보를 자동으로 추론할 수 있습니다.

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

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 변환기를 제공합니다.

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는 createFontStack라는 @capsize/core 패키지의 일부로, 글꼴 스택 (font-family 속성)을 지정하는 것과 동일한 순서로 글꼴 측정항목 배열을 허용합니다.

Capsize 사용에 대한 문서는 여기에서 참조하세요.

다음 예를 생각해 보세요. 원하는 웹 글꼴은 Lobster이며 Helvetica Neue로 이동한 다음 DIFM으로 대체됩니다. 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%;
     }
   }
 ]
}

fontFamily 및 fontFaces 코드를 CSS에 추가해야 합니다. 다음 코드는 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));

감사의 말씀

UnsplashAlexander Andrews의 히어로 이미지입니다.