이전 블로그에서는 Next/Image를 사용하여 드라마틱한 성능 개선을 이루었다.
이번 블로그에서는 드라마틱한 성능 개선은 아니겠지만 폰트 최적화 및 트리 셰이킹을 통해 좀 더 개선을 해보려고 한다.
font 최적화
Lighthouse에서 위와 같은 경고를 볼 수 있었다.
이에 관련하여 해결하기 위해 FOIT와 FOUT에 대해 알아볼 필요가 있었다.
FOIT(Flash of Invisible Text)
FOIT란 웹폰트가 로드되는 동안 텍스트가 일시적으로 사라지는 현상을 말한다. font-display 속성을 지정해 주지 않으면 웹폰트가 아직 로드되지 않은 상태에서 해당 폰트를 사용하는 텍스트가 기본 시스템 폰트로 표시되다가, 웹폰트가 로드되고 나서야 해당 폰트로 교체되는 현상이라고 할 수 있다.
FOUT(Flash of Unstyled Text)
Fout란 웹폰트가 로드되기 전에 일시적으로 기본 시스템 폰트로 텍스트를 표시하는 것을 말한다. 웹폰트가 로드되면 해당 폰트로 텍스트가 교체된다. FOUT는 FOIT와 달리, 로드되기 전에 텍스트를 표시하여 사용자가 폰트 교체를 인지할 수 있다.
위 lighthouse 경고는 즉 폰트가 로드되는 동안 FOIT 현상이 발생하므로 font-display 설정을 통해 FOUT 현상으로 대체하도록 경고하는 것이였다.
Next에서는 폰트를 최적화 하는 next/font를 제공하고 있다.
우리 서비스는 Pretendard-Variable를 사용하고 있는데, next/font가 가장 최적화에 자신있어하는 google/font에는 variable 폰트를 찾을 수 없었다.
그러나 다행히 로컬 폰트 파일도 최적화 할 수 있는 next/font/local도 제공하고 있었다.
next/font 를 사용하면 외부적으로 네트워크 요청을 할 필요 없이 자동으로 최적화를 해준다.
뿐만 아니라 layout shift의 발생 걱정 또한 하지 않아도 된다고 공식 문서에 나와있다.
기본적으로 next/font는 어떠한 글꼴을 사용하는 곳에서 preload 되며, font-display가 optional로 설정되어 있다.
다음은 font-display: optional 의 특징이다.
- 네트워크 상황에 따라 브라우저가 글꼴 다운로드 여부를 결정한다는 점이다.
- 시스템 글꼴을 유지할지 웹 글꼴을 적용할지 브라우저가 결정한다.
- 만약 네트워크 상황이 좋지 않으면, 브라우저는 로딩된 웹 폰트를 캐시에 저장하고 시스템 글꼴을 사용한다.
- 그리고 다음 방문시에 캐시된 웹 폰트를 적용한다.
그럼 이제 폰트 최적화를 하러 가보자!
현재 서비스의 기본적인 설정은 다음 블로그에 나와있다.(https://doyourbestcode.tistory.com/131)
나는 글꼴을 모든 파일에서 사용해야 하므로 가장 바깥의 layout.tsx 파일에서 작업을 진행해 주었다.
import localFont from "next/font/local";
const globalFont = localFont({
src: [
{
path: "../common/assets/fonts/pretendard/Pretendard-Bold.woff2",
weight: "700",
style: "normal",
},
{
path: "../common/assets/fonts/pretendard/Pretendard-SemiBold.woff2",
weight: "600",
style: "normal",
},
{
path: "../common/assets/fonts/pretendard/Pretendard-Medium.woff2",
weight: "500",
style: "normal",
},
{
path: "../common/assets/fonts/pretendard/Pretendard-Regular.woff2",
weight: "400",
style: "normal",
},
],
variable: "--pretandard-variable",
});
위와 같이 폰트 파일들이 저장되어 있는 경로를 불러다가 next/font/local의 규칙에 맞게 세팅해준 뒤, variable 이름을 설정해 주었다.
<body className={twMerge("w-[100%] h-[100%]", globalFont.variable)}>
...
</body>
그 후, 세팅한 variable 이름에 접근할 수 있도록 위와 같이 body 태그 className에 추가해주었다.
@font-face {
font-family: var(--pretandard-variable);
font-style: normal;
}
위는 global.css 에서 import 하고 있는 font.css 파일의 내용이다.
전 단계에서 설정해준 variable 이름을 통해 font-family를 설정해 주었다.
module.exports = {
...
plugins: [
plugin(({ addUtilities }) => {
addUtilities({
".header-light": {
fontFamily: "var(--pretandard-variable), sans-serif",
fontWeight: "500",
fontSize: "2rem",
lineHeight: "140%",
letterSpacing: "-2%",
},
...
}
마지막으로 tailwind.config.ts 파일이다.
나는 addUtilities를 통해 이름을 따로 설정해준 뒤 해당 이름을 그대로 부여하는 식으로 작업을 하고 있으므로, 위처럼 @font-face의 font-family 이름을 통해 설정해 주었다.
이제 폰트 최적화를 마쳤으므로 lighthouse 측정을 다시 해보았다.
위의 로컬 폰트에 관한 FOIT 에러가 사라진 것을 확인할 수 있다!!
다만, 사진 슬라이더 기능을 사용할 때 react-slick 라이브러리를 사용하는데 해당 css파일에서 정의되어 있는 CSS 내용으로 인해 경고가 아직 저 한줄은 뜨고있는 상황이다.
해당 내용에 대해 아직 해결방법을 찾지 못했지만 로컬 폰트에 대한 FOUT는 잘 적용되므로 나름 만족한다.
위 라이브러리 CSS에 대한 FOUT 처리에 대해 알게되면 꼭 업데이트 해야겠다.
트리 셰이킹(Tree shaking)
트리 쉐이킹은 번들링 과정에서 불필요한 코드 & 사용되지 않는 모듈을 식별하고 제거하는 기법이다.
트리 셰이킹을 통해 서비스를 조금이라더 더 개선해보자.
Next에서는 주로 트리셰이킹을 bundle-analyzer로 진행한다.
yarn add @next/bundle-analyzer
위 명령어로 우선 budle-analyzer를 설치해준 뒤
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
const nextConfig = {
...
};
module.exports = withBundleAnalyzer(nextConfig);
다음처럼 next.config.js 파일을 설정해주자.
마지막으로 package.json 파일로 가서
"analyze": "ANALYZE=true next build",
위와 같이 scripts에 명령어를 추가해주면 모든 준비가 끝났다.
yarn analyze
해당 명령어를 입력하여 트리 셰이킹 시도를 해볼 수 있다.
명령어 실행 후 나오는 client.html 을 봐보았다.
위와 같이 시각적인 결과를 활용하여 불필요한 코드로 인해 크기를 많이 잡아먹는 모듈을 찾아낼 수 있다.
사실 전체적으로 봤을 때 그렇게 크게 눈에 띄는 내용은 없었다.
다만 위의 홈 페이지에서 PlaceInfoCard와 CurationInfoCardLight 컴포넌트를 렌더링 하게 되는데, 슬라이더 형식으로 구현이 되어 있기 때문에 아직 슬라이더에 노출되지 않은 컴포넌트까지 한번에 모두 가져올 필요는 없었다.
기본적으로 Next는 페이지 별 코드 스플리팅(Code Splitting)을 지원하지만, 그 외에도 당장 필요가 없다면 dynamic import를 통해 코드 스플리팅 처리가 가능하다.
const PlaceInfoCard = dynamic(
() => import("@feature/place/components/PlaceInfo/molecules/PlaceInfoCard")
);
위와 같이 처리해 준 후 결과를 봐보았다.
위처럼 빌드되는 JS 파일의 크기가 172 kB에서 158 kB로 줄어든 것을 확인할 수 있다.
빌드되는 JS 파일의 크기가 작을 수록 빠르게 서빙할 수 있으므로, 꽤나 중요한 작업이라고 할 수 있을 것 같다.
Link prefetch
Lighthouse를 떠나서 next/link가 제공하는 prefetch 기능을 잘 활용한다면 유저 입장에서 매우 빠른 서비스를 제공한다고 느끼게 할 수 있다.
Next 14 에서는 next/router를 사용하지 못하므로, 어떤 컴포넌트를 눌러서 이동해야 한다면 router.push 도 좋지만 prefetch 기능을 제공해주는 next/link를 적극적으로 활용해야 될 것 같다.
next 공식문서를 보면 next/link를 사용할 시, Link 컴포넌트가 viewport 안에 들어오면 백그라운드에서 href 에 선언된 라우트 및 관련 데이터들을 미리 가져온다고 한다.
prefetch 값에 따른 동작은 다음과 같다.
그냥 prefetch에 대한 선언은 따로 없이 Link 컴포넌트를 사용만 해도 static routes는 데이터를 포함하여 전부 미리 가져오고, dynamic routes에 대해서는 데이터가 차별화 되므로 가장 가까운 loading.js 를 미리 가져온다고 나와있다.
그런데 테스트를 해보니 true를 선언해주는 것과 안해주는 것은 체감상 너무 달랐다.
꼭 prefetch를 해줘야 하는 route에 대해서는 그냥 true로 선언해주는 것이 나을 것 같다.
나는 가장 클릭할 확률이 높은 Footer 에 있는 섹션들과 관련된 route는 미리 prefetch 하기로 하였고, 해당 Link 컴포넌트에 prefetch={true}를 선언해 주었다.
선언 전과 후, 페이지가 로딩되는 속도가 확연히 다른 것을 확인할 수 있다.
물론 prefetch를 불필요하게 사용한다면 어쨋든 자원을 사용하는 것이므로 좋지 않겠지만, 필요한 부분에 잘 적용한다면 체감상 엄청난 효과를 볼 수 있을 것 같다.
이처럼 이미지, 폰트, 트리 셰이킹, Link prefetch 의 측면에서 UX 최적화를 위해 달려나가 보았다.
아직 부족한 부분이 많고, 개선할 부분이 많겠지만 최고의 서비스를 제공하기 위해 차근차근 나아가야겠다!
참고
https://velog.io/@devhyuk/%EC%B5%9C%EC%A0%81%ED%99%94-font-display-%EC%B5%9C%EC%A0%81%ED%99%94
[최적화] font-display 최적화
웹폰트가 로드되는 동안 텍스트가 계속 표시되는지 확인하기(참고URL) ⏺ font-display font-display는 웹폰트가 로드되는 동안 텍스트를 어떻게 처리할지 지정하는 CSS 속성입니다. 다음은 font-display 속
velog.io
[CSS] font-display, 글꼴 렌더링 방식을 변경하는 방법
🙂 이번 시간에는 글꼴 랜더링 방식을 컨트롤 하는, font-display에 대해 알아보았다! 1. 글꼴 랜더링 방식 우리가 어떤 사이트에 들어갔을 때 글자가 늦게 나타나거나, 혹은 글꼴이 나중에 적용되
mong-blog.tistory.com
https://cheolsker.tistory.com/87
[Next.js] 페이지 성능 개선하기 2 - 번들 사이즈 개선, Tree Shaking
지난 시간에 측정했던 성능 점수입니다. (모바일 기준) 지난 시간에 성능 개선을 시도했던 흔적 👇 [Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드 최근 들어 성능 최적화에 대한 고민을
cheolsker.tistory.com
https://nextjs.org/docs/app/api-reference/components/link#prefetch
Components: <Link> | Next.js
Enable fast client-side navigation with the built-in `next/link` component.
nextjs.org
'FrontEnd > Next' 카테고리의 다른 글
Next 성능 개선 일지 1 (Feat: Lighthouse, Next/Image) (2) | 2024.04.18 |
---|---|
Next 에서 소셜 로그인 구현하기(Feat : Cookie) (4) | 2024.03.14 |
Next 에서 데이터 관리하기 (Feat : Cache) (2) | 2024.03.09 |
Next + TailwindCSS 세팅하기 (2) | 2023.12.28 |