프론트엔드 개발을 하다 보면 한번쯤은 ‘성능을 개선 할 예정입니다.’라는 말을 하게된다. 근데 문제는? 뭘 어떻게 해야 성능이란 것이 개선되냐 이말이다. 나도 이전까지는 최적화 하면 렌더링 최적화만 생각했는데 이번에 학습하면서 렌더링이 전부는 아니라는 것을 알게 되었다. 우테코 미션과 팀프로젝트 성능최적화를 하며 학습한 내용을 성능 최적화를 하게 될 미래의 나를 위해 작성한다.
프론트엔드 성능 = 로딩성능 + 렌더링 성능
(페이지가 얼마나 빨리 보이나 + 사용자 인터랙션에 얼마나 빠르고 효율적이게 반응하나)
성능을 낮추는 문제들은 대체로 비슷한 유형이다. 다만 해결책은 시간이 지나면 지날수록 계속 발전해 업데이트된다. 그러므로 해결책을 외우려 하지 말고 무슨 문제인지를 알아채는 것이 중요하다.
에 대해 살펴보면 좋을 키워드와 지금의 해결 방법들을 알아보도록 하자.
어떤 타입의 요청이 큰가?
웹 페이지에서 요청하는 리소스 타입
minify란 경량화(압축)을 말한다. 코드 안에 있는 불필요한 공백, 들여쓰기, 변수명, 사용하지 않는 변수명 삭제 등을 포함한다.
uglify란 난독화를 말한다. 우리가 이쁘게 이름지어준 함수명과 변수명 같은 것을 오히려 읽을 수 없도록 변환한다. 불필요한 데이터 삭제와 더불어 효과적인 보안을 노릴 수 있다.
//webpack.config.js
{
...
module: {
rules: [
...
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
]
},
optimization: {
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()]
}
}
근데 사실 webpack 4부터 production 환경에서 기본으로 js minify와 uglify를 해준다. ㅋㅋ 그래서 css 파일만 minify 해주면 된다. (CSS in JS 는 babel transpile 과정에서 minify) 최종적으로 아래처럼 하면됨
//webpack.config.js
{
...
module: {
rules: [
...
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
]
},
optimization: {
minimizer: ['...', new CssMinimizerPlugin()]
}
}
소스코드를 압축하는 방식이다. 모던 브러우저에서는 모두 내장되어있고 AWS를 사용한다면 CDN 설정으로 압축할 수 있다. 그게 아니라 빌드시 압축하고 싶다면 compressionWebpackPlugin을 이용해 소스코드를 압축 할 수 있다.
//webpack.config.js
{
...
plugins: [
...
new CompressionPlugin({
algorithm: 'gzip'
}),
]
}
<picture>
<source
type="image/webp"
src={heroLgImage}
srcSet={`${heroSmImage} 500w, ${heroMdImage} 1000w,${heroLgImage} 2000vw`}
/>
<img className={styles.heroImage} src={heroImage} alt="hero image" />
</picture>
이렇게 하면 webp를 지원하는 경우 뷰 크기에 따라 source 태그의 srcSet에 있는 이미지가 나오고 webp를 지원하지 않는 경우는 img 태그에 있는 이미지가 나온다.
<video className={styles.featureImage} autoPlay loop muted playsInline>
<source src={webmSrc} type="video/webm" />
<source src={mp4Src} type="video/mp4" />
</video>
다만 이미지 크기가 작으면 무조건 좋다고 할 순 없다. 사용자 경험과 밀접하게 관련있기 때문이다. 그래서 상황에 맞게 알잘딱으로 최적화 해주자^0^
웹 폰트 최적화는 아래 글이 솔직히 짱짱맨
폰트 중에 자주 사용하지 않는 글씨(갂 갃 갍 뷁..)들을 제거한 폰트를 사용하면 용량을 줄일 수 있는데 바로 subset이 붙어있는 걸 사용하면 된다.
팀바팀은 위 글처럼 했는데 용량이 그래도 커서 필요한 폰트 파일을 다운로드해서 CDN 캐쉬 적용하고 사용했다.
https://github.com/woowacourse-teams/2023-team-by-team/pull/641
//App.css
@font-face {
font-family: 'Pretendard';
font-weight: 500;
font-display: swap;
src:
local('Pretendard Medium'),
url('assets/fonts/Pretendard-Medium.subset.woff2') format('woff2');
}
화면이 렌더링 되고 폰트가 불러와져서 깜빡거리는 현상이 발생했다 이를 해결하기위해 폰트를 preload 하기로 결정. 우리는 @vue/preload-webpack-plugin를 설치해서 해결했다.
//webpack.config.js
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
..
module.exports = {
plugins: [
...
new PreloadWebpackPlugin({
rel: 'preload',
fileWhitelist: [/\.woff2$/],
include: 'allAssets',
as: 'font',
}),
],
}
Tree shaking이란 사용되지 않는 코드를 제거하기 위해 JavaScript 컨텍스트에서 일반적으로 사용되는 용어이다.
참고로 webpack5부터는 기본으로 알아서 제거해준다. 그래도 설정을 직접해야한다 싶으면 아래처럼 하면 된다.
//package.json
"babel": {
"presets": [
...
{
"modules": false //balel이 commonJS로 변환하기도 하는데 그러면 sideEffects옵션이 작동하지 않기 때문에 설정해줌
}
],
}
"sideEffects": [
"*.css"
] // 배열 안에 있는 확장자를 제외한 모든 파일에 대해 sideEffect를 false로 한다.
// 만약 그냥 전체로 sideEffect false하고 싶으면 "sideEffects" : false
현재 페이지에서 필요한 리소스만 받아올 수 있도록 React lazy와 suspense를 사용한다.
lazy를 사용하기 위해서는 suspense가 필수!
import { lazy, Suspense } from 'react';
const Home = lazy(() => import('./pages/Home/Home'));
const Search = lazy(() => import('./pages/Search/Search'));
const App = () => {
return (
<Router>
<Suspense fallback={<div>로딩즁!</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/search" element={<Search />} />
</Routes>
</Suspense>
</Router>
);
};
이건 CDN 설정으로 할 수 있다.
프로젝트에 따라 캐시정책이 서로 다른 경우가 많으니 맞는 걸 적용하면 된다.
그래서 성능 최적화를 진행하면 뭐가 많이 개선될까? 답은 된다. 좀 크게…
팀바팀에서 가장 요청이 활발한 페이지인 모아보기 페이지를 기준으로 Lighthouse를 측정했다.
우리나라처럼 인터넷이 좋은 곳에서는 크게 체감 못할 수 있지만 조금만 인터넷 느리게하던가 캐쉬사용중지로 테스트하면 체감이 크다.ㅎㅎ 다들 성능최적화 열심히 하자~! 아자아자 홧팅!
잘봤습니다 :) 정리가 잘 되어있어서 읽기 좋네요 👍