Next.js: Local Font를 tailwindCSS와 같이 사용하면 발생하는 이슈

ain·2023년 5월 28일
7

NextJS

목록 보기
2/2
post-thumbnail

본 글은 Next.js 12버전을 기반으로 작성된 글입니다.

+ 2023.06.14 업데이트
Next.js 공식 디스코드 help-forum에 문의한 결과 Next.js contributor 중 한 분이 직접 코멘트를 달아주셨다.
우선 웹 페이지 endpoint에 /font가 들어갈 수 있어서 pages 폴더 안에 폰트 파일들을 넣기보다는 바깥으로 빼는 게 좋다고 한다.
그리고 내가 마주한 문제는 Next.js의 버그인 것 같다고 한다.
아직 정확한 답이 나오지 않은 상태여서 추후 다시 업데이트하겠다!

Next.js의 폰트 최적화

Next.js를 사용한다면 이미지 최적화와 마찬가지로 폰트 최적화도 빼먹을 수 없다.
폰트 최적화를 하려면 next/font를 다운로드 받아서 사용할 수 있는데, 여기서 지원해주는 기능은 구글 폰트(Google Fonts)와 로컬 폰트(Local Fonts)이다. (참고로 Next.js 13버전은 next/font가 처음에 같이 설치 된다고 한다.)

본 글에서는 구글 폰트는 다루지 않고, 로컬 폰트를 적용하기 전과 적용하면서 발생한 이슈에 대해 다뤄볼 예정이다.

로컬 폰트 이전의 @font-face

Next.js의 로컬 폰트를 사용하기 전에는 global.css에 @font-face를 만들어서 폰트를 cdn으로 .eot, .woff2, .woff, .ttf 파일 전부 가져와서 설정해주고 그 폰트를 htmlbody, font-family 속성에 넣어주었다.

cdn으로 가져오다보니 페이지를 딱 열었을 때 기본 폰트였다가 새로 적용한 폰트를 적용할 때 깜빡임이 발생한다.(Flash of Unstyled Text). 물론 페이지 이동 시에는 그런 깜빡임이 없고 새로고침 할때만 보이지만 나에겐 너무 거슬렸기에 다른 방법을 찾아야 했다.

그러다가 Next.js의 로컬 폰트 관련 공식 문서를 보고 바로 이 방법으로 채택하였다.

@next/font 채택 이유

Next.js 공식 문서에 의하면:

  • next/font will automatically optimize your fonts (including custom fonts) and remove external network requests for improved privacy and performance.
  • next/font includes built-in automatic self-hosting for any font file. This means you can optimally load web fonts with zero layout shift, thanks to the underlying CSS size-adjust property used.
  • This new font system also allows you to conveniently use all Google Fonts with performance and privacy in mind. CSS and font files are downloaded at build time and self-hosted with the rest of your static assets. No requests are sent to Google by the browser.
    Next.js - Font Optimization

요약해보자면, next-font를 사용하면 자동으로 최적화 해주고, 레이아웃 이동 없이 폰트를 적용할 수 있다고 한다. 만약 구글 폰트를 사용한다면 구글에 폰트를 받아오는 요청을 따로 하지 않고 CSS와 폰트 파일이 빌드타임에 같이 다운로드 된다고 한다.
빌드타임에 같이 받아오기 때문에 폰트가 다 적용된 다음에 요소들이 화면에 뿌려지면서 깜빡임 현상이 없어진다.

실제로 적용해보았을 때, 폰트가 다 로드되면 화면에 뿌려지기 때문에 FOUT 현상은 없어진다.

문제의 다이나믹 라우팅

next/font를 사용하여 FOUT현상을 해결하였다고 안심했는데, 며칠 후 다이나믹 라우팅 페이지로 이동 시에 로컬 폰트가 적용이 안되는 이슈를 발견하였다. 이번에는 FOUT 현상이 일어나는 것도 아니고 그냥 로컬 폰트가 적용이 안된다.

찾아보니, 다이나믹 라우팅 페이지로 이동 시 페이지가 다시 새로 로드 되기 때문에 폰트도 다시 받아온다는데 다시 받아올 때 폰트가 페이지보다 느리게 로드 되어서 기본폰트가 적용된다는 것이다.
다이나믹 라우팅 페이지로 이동 시, 페이지가 새로 로드 되는건 아니고

이를 해결하기 위해서 몇가지를 시도해보았다.

시도

  • display: 'swap' 적용 ✅
  • localFonts에 variables를 적용하여 main태그에 넣기
  • global.css font-family속성에 variable 적용
  • _document.tsx html에 적용

display에 'swap' 적용

import localFont from '@next/font/local';

const myFont = localFont({
  src: [
    {
      path: './fonts/myFont.woff',
      weight: '400',
    },
    {
      path: './fonts/myFont.woff',
      weight: '600',
    },
    {
      path: './fonts/myFont.woff',
      weight: '700',
    },
  ],
  display: 'swap',
});

font-display라는 css 속성이 있다. 이 속성은 폰트가 로드 되기 전후를 감지하여 로컬 폰트를 자동으로 로드 시키는 속성이다.
속성값은 총 5개로 block, swap, auto, fallback, 그리고 optional이 있다. 이 5개 중 브라우저의 기본동작을 따르는 auto를 제외하고 swap 속성값이 그나마 기본폰트를 보여줬다가 로컬 폰트로 보여주는 속성이다. 나머지는 로컬 폰트가 로드되지 않으면 페이지 이동 시 깜빡임이 심하기 때문에 swap으로 적용하였다.

하지만, 다이나믹 라우팅 페이지에서 FOUT가 된다면 첫 화면에서 FOUT 현상이 발생하는 것과 거의 똑같은 상황이 아닌가?
라는 생각이 들어 FOUT 현상을 아예 없애버리기 위해서 갖은 시도를 해보았다.

localFonts variables 만들기

next/font의 localFonts를 만들 때에는 객체안에 variables를 넣어 변수처럼 사용할 수 있다고 한다.

const myFont = localFont({
  ...
  variable: '--font-myfont',
});

하지만 변수를 설정하는 것부터 적용이 안된다. Next.js의 공식문서에 나와있는대로 적용해봤는데 안된다... 뭔가 단단히 꼬인듯하다. 이 부분은 나중에 작은 프로젝트에서 다시 시도해보려고 한다.

global.css font-family에 variables 적용

이 부분도 마찬가지로 variables가 적용이 안되기 때문에 시도 실패다.

_document 파일에서 html에 폰트 적용

만약 루트에 폰트를 적용해야 한다면, html에 바로 적용해야겠다는 생각이 들어 _document에 html에 className으로 적용해보았다.

import { Html, Head, Main, NextScript } from 'next/document';
import Script from 'next/script';
import localFont from '@next/font/local';

const myFont = localFont({
  src: [
    {
      path: './fonts/myFont.woff',
      weight: '400',
    },
    {
      path: './fonts/myFont.woff',
      weight: '600',
    },
    {
      path: './fonts/myFont.woff',
      weight: '700',
    },
  ],
  display: 'swap',
});
export default function Document() {
  return (
    <Html lang="ko" className={`${myFont.className}`}>
      <Head>
        <Script></Script>
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

하지만 _document 파일은 외부 리소스를 동적으로 로드하는 작업은 지원하지 않는다. 때문에 로컬 폰트를 로드하려고 하면 에러가 발생한다.

tailwindCSS의 문제?

tailwindCSS는 빌드타임에 css파일이 만들어지는 것이 아니라 동적으로 className에 스타일링 값을 집어넣는 것이기 때문에 다이나믹 라우팅 시 빌드 되지 않는것으로 판단된다. 그래서 @font-face를 global.css에 넣으면 다이나믹 라우팅 페이지로 이동할 때도 잘 적용이 되는데 css파일에서 폰트를 로드하는게 아닌 _app 파일에서 동적으로 className을 넣으니 발생하는 문제같다.

현재로써는 font-display 속성으로 초기 페이지에는 바로 적용되고, 다이나믹 라우팅 페이지에서는 기본 폰트가 적용됐다가 로컬폰트가 로드되면 적용하는 방식을 선택했지만, 썩 마음에 들지 않는다.

tailwindCSS의 jit모드로 시도해보고, 안되면 font-face로 다시 바꿔야 할 듯하다. next/font 쓰고 싶어서 고집부리다가 UX만 망칠 것 같다. 이래서 한가지 기술에 고집부리지 말라고 하나보다.ㅎㅎ!

FOUT 현상을 없애고 UX를 개선하기 위해 시도를 더 해볼 것이며, 이에 대한 과정은 2편에서 풀도록 하겠다.

참고

profile
프론트엔드 개발 연습장 ✏️ 📗

0개의 댓글