blog-imgDucklog

next 14 폰트 레이아웃 쉬프트 해결하기

next 14 폰트 레이아웃 쉬프트 해결하기

Layout Shift?

레이아웃 쉬프트란 웹 페이지의 렌더링 과정에서 발생하는 사용자 경험과 관련된 성능 지표 중 하나다. Core Web Vitals 중 하나로, Google에서 웹 페이지의 사용자 경험을 평가하는 데 사용되는 중요한 지표 중 하나다.

레이아웃 쉬프트는 웹 페이지가 로드되는 동안 요소들의 위치가 변경되어 사용자 경험에 영향을 미치는 현상을 나타낸다. 사용자가 페이지를 열었을 때, 원치 않는 레이아웃의 변화가 발생하면 사용자가 의도한대로 상호작용하는 것을 방해할 수 있다.


레이아웃 쉬프트는 다음과 같은 상황에서 발생한다.

  1. 이미지의 크기를 지정하지 않고 로딩: 이미지가 로드되기 전에 해당 이미지의 크기를 명시적으로 지정하지 않으면, 이미지가 로드되면서 레이아웃이 변경될 수 있다.

  2. 동적으로 로드되는 콘텐츠: 페이지가 로드된 후에 JavaScript 또는 비동기적으로 로드되는 콘텐츠가 있다면, 이로 인해 레이아웃이 변할 수 있다.

  3. 폰트의 지연된 로딩: 페이지에 사용된 폰트가 로드되기 전에 기본 폰트로 표시되다가 폰트가 로드되면 레이아웃이 변할 수 있다.

Next + tailwindcss를 이용해서 개발을 하고 있는 도중, 새로고침을 하게 되면 위 3번 폰트 레이아웃 쉬프트가 발생하고 있었다. 해당 폰트는 웹 폰트로 사용하고 있었다. 해당 레이아웃 쉬프트를 없애기 위해 구글링하던 도중, next 13부터 next font를 지원해주고 있었다. next/font를 사용하면 최적화할 수 있을 뿐만이 아니라 레이아웃 없이 최적으로 폰트를 로드할 수 있다고 해서 사용했다.

레이아웃 쉬프트 발생 화면

우선 기존 웹 폰트를 걷어내고, 로컬 폰트로 받아오기 위해서 기존 웹폰트 코드들을 삭제해줬다.

@font-face {
  font-family: 'SCoreDream';
  font-weight: 100;
  font-style: normal;
  src: url(https://cdn.jsdelivr.net/gh/webfontworld/SCoreDream/SCoreDream1.woff2) format('woff2'), url(https://cdn.jsdelivr.net/gh/webfontworld/SCoreDream/SCoreDream1.woff)
      format('woff');
  font-display: swap;
}

위 코드들을 삭제해줬다. 그리고 해당 폰트를 저장한 뒤 next app 라우터를 사용하기 때문에, app/fonts 폴더에 폰트들을 놓아줬다.

그 후 layout.tsx에서 폰트 설정을 해줬다. _ layout.tsx _

import type { Metadata } from 'next';
import './globals.css';
import 'react-toastify/dist/ReactToastify.css';
import { ToastContainer } from 'react-toastify';
import localFont from 'next/font/local';

export const metadata: Metadata = {
  title: 'Petmunity',
  description: '',
};

const myFont = localFont({
  src: [
    {
      path: './fonts/SCDream1.otf',
      weight: '100',
    },
    {
      path: './fonts/SCDream2.otf',
      weight: '200',
    },
    {
      path: './fonts/SCDream3.otf',
      weight: '300',
    },
    {
      path: './fonts/SCDream4.otf',
      weight: '400',
    },
    {
      path: './fonts/SCDream5.otf',
      weight: '500',
    },
    {
      path: './fonts/SCDream6.otf',
      weight: '600',
    },
    {
      path: './fonts/SCDream7.otf',
      weight: '700',
    },
    {
      path: './fonts/SCDream8.otf',
      weight: '800',
    },
    {
      path: './fonts/SCDream9.otf',
      weight: '900',
    },
  ],
  display: 'swap',
  variable: '--font-scdream',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko" className={`bg-gray-50 ${myFont.className} `}>
      <body className="md:max-w-[375px] w-full relative m-auto min-h-screen bg-white p-0 font-sans lining-nums text-gray-900 outline-none">
        <div id="modal-root" />
        {children}
        <ToastContainer position="top-right" autoClose={5000} />
      </body>
    </html>
  );
}

tailwindcss를 사용하고 있기 때문에, variable로 폰트 변수 이름을 지정해줬다.

_ tailwind.config.ts _

const config: Config = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    fontFamily: {
      sans: ["var(--font-scdream)"],
    },
    ... 생략

이런식으로 아까 만들어준 폰트 변수를 넣어주면 사용할 수 있게 된다!

_ 폰트 최적화 후 _

아까랑 다르게 폰트 레이아웃 쉬프트 현상이 사라졌다.