프론트엔드 웹 성능 최적화

RuLu·2023년 9월 27일
40

Etc.

목록 보기
9/13
post-thumbnail

프론트엔드 개발을 하다 보면 한번쯤은 ‘성능을 개선 할 예정입니다.’라는 말을 하게된다. 근데 문제는? 뭘 어떻게 해야 성능이란 것이 개선되냐 이말이다. 나도 이전까지는 최적화 하면 렌더링 최적화만 생각했는데 이번에 학습하면서 렌더링이 전부는 아니라는 것을 알게 되었다. 우테코 미션과 팀프로젝트 성능최적화를 하며 학습한 내용을 성능 최적화를 하게 될 미래의 나를 위해 작성한다.

프론트엔드 성능 = 로딩성능 + 렌더링 성능
(페이지가 얼마나 빨리 보이나 + 사용자 인터랙션에 얼마나 빠르고 효율적이게 반응하나)

성능 측정 도구

  • Lighthouse (개발자도구)
  • WebPageTest
    • '프랑스 파리에서 Fast 3G 환경으로 접속했을 때

성능을 낮추는 문제들

성능을 낮추는 문제들은 대체로 비슷한 유형이다. 다만 해결책은 시간이 지나면 지날수록 계속 발전해 업데이트된다. 그러므로 해결책을 외우려 하지 말고 무슨 문제인지를 알아채는 것이 중요하다.

  1. 요청 크기 줄이기
  2. 필요한 것만 필요한 때에 요청하기
  3. 같은 건 매번 새로 요청하지 않기
  4. 최소한의 변경만 일으키기

에 대해 살펴보면 좋을 키워드와 지금의 해결 방법들을 알아보도록 하자.

로딩 성능 개선 - 요청 크기 줄이기

어떤 타입의 요청이 큰가?

웹 페이지에서 요청하는 리소스 타입

  • 텍스트 컨텐츠 (소스코드), 이미지, 폰트

텍스트 컨텐츠(소스 코드)

minify & uglify

minify란 경량화(압축)을 말한다. 코드 안에 있는 불필요한 공백, 들여쓰기, 변수명, 사용하지 않는 변수명 삭제 등을 포함한다.

uglify란 난독화를 말한다. 우리가 이쁘게 이름지어준 함수명과 변수명 같은 것을 오히려 읽을 수 없도록 변환한다. 불필요한 데이터 삭제와 더불어 효과적인 보안을 노릴 수 있다.

  1. js minify&uglify 를 위해 minimize 설정을 true, TerserPlugin설치 후 minimizer에 추가
  2. css minify를 위해 MiniCssExtractPlugin를 설치 후 MiniCssExtractPlugin.loader사용, optimization의 minimizer에 CssMinimizerPlugin() 추가
    • 주의사항) MiniCssExtractPlugin.loader는 style-loader와 함께 사용 불가능
//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()]
  }
}

gzip / brotli

소스코드를 압축하는 방식이다. 모던 브러우저에서는 모두 내장되어있고 AWS를 사용한다면 CDN 설정으로 압축할 수 있다. 그게 아니라 빌드시 압축하고 싶다면 compressionWebpackPlugin을 이용해 소스코드를 압축 할 수 있다.

//webpack.config.js
{
...
	plugins: [
		...
		new CompressionPlugin({
      algorithm: 'gzip'
    }),
	]
}

이미지

이미지 포맷 점검 & 압축

  • PNG와 JPEG 인 이미지가 있을 경우 WebP로 전환한다면 JPEG보다 25~34%, PNG 보다 26% 작아진다.
  • 또한 너무 큰 해상도와 소스크기라면 이미지 압축도 생각해 보아야한다.
  • 모바일에서는 PC 만큼의 해상도가 필요없는데 이런경우 srcSet을 사용하면 좋다.
<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 태그에 있는 이미지가 나온다.

  • GIF 은 대부분 매우 큰 용량이다. 이를 video태그를 사용해 gif처럼 구현할 수 있는데 mp4 혹은 webm으로 변환해 구현하면 용량을 줄일 수 있다.
<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',
    }),
	],
}

로딩 성능 개선 - 필요한 것만 필요한 때에 요청

전혀 필요없는 리소스

불필요한 리소스 요청이 있는 지 점검하기

  • ex) 지금은 사용하지 않는 소스코드들,,

Tree shaking

Tree shaking이란 사용되지 않는 코드를 제거하기 위해 JavaScript 컨텍스트에서 일반적으로 사용되는 용어이다.

참고로 webpack5부터는 기본으로 알아서 제거해준다. 그래도 설정을 직접해야한다 싶으면 아래처럼 하면 된다.

//package.json
"babel": {
    "presets": [
      ...
      {
        "modules": false //balel이 commonJS로 변환하기도 하는데 그러면 sideEffects옵션이 작동하지 않기 때문에 설정해줌
		  }
    ],
}
"sideEffects": [
    "*.css"
  ] // 배열 안에 있는 확장자를 제외한 모든 파일에 대해 sideEffect를 false로 한다. 
// 만약 그냥 전체로 sideEffect false하고 싶으면 "sideEffects" : false

지금 필요없는 리소스

  • dynamic import
  • lazy loading & intersectionObserver
  • (React) code splittting w/React.lazy, Suspense

code splittting

현재 페이지에서 필요한 리소스만 받아올 수 있도록 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>
  );
};

이미지

image sprite

  • 요청 하나로 묶기

다른 방법으로 이미지 대체하기

  • data URI, CSS, SVG

로딩 성능 개선 - 같은 건 매번 새로 요청하지 않기

이건 CDN 설정으로 할 수 있다.

프로젝트에 따라 캐시정책이 서로 다른 경우가 많으니 맞는 걸 적용하면 된다.

렌더링 성능 개선 - 최소한의 변경만 일으키기

React Memo

Layout Shift 줄이기

  • 이미지 width, height 지정
  • 컴포넌트의 위치를 이동시켜야한다면 translate를 사용한다.

성능 최적화 전 후,

그래서 성능 최적화를 진행하면 뭐가 많이 개선될까? 답은 된다. 좀 크게…

팀바팀에서 가장 요청이 활발한 페이지인 모아보기 페이지를 기준으로 Lighthouse를 측정했다.

최적화 이전

  • 최적화 이전 폰트 (깜빡이는 현상 발생)

  • 최적화 이전 폰트 파일 용량 (694 ~ 802kB)

최적화 이후

  • 최적화 이후 (깜빡이는 현상 해결)
  • 최적화 이후 폰트 파일 용량 (preload + subset, 224 ~ 273kB)

우리나라처럼 인터넷이 좋은 곳에서는 크게 체감 못할 수 있지만 조금만 인터넷 느리게하던가 캐쉬사용중지로 테스트하면 체감이 크다.ㅎㅎ 다들 성능최적화 열심히 하자~! 아자아자 홧팅!

profile
프론트엔드 개발자 루루

10개의 댓글

comment-user-thumbnail
2023년 9월 29일

잘봤습니다 :) 정리가 잘 되어있어서 읽기 좋네요 👍

1개의 답글
comment-user-thumbnail
2023년 10월 6일

자주 놀러올게요

1개의 답글
comment-user-thumbnail
2023년 10월 6일

참고해서 성능 최적화 해보겠습니다 감사합니다 :)

1개의 답글
comment-user-thumbnail
2023년 10월 9일

좋은 글 잘 봤습니다 종종 보겠습니다!

1개의 답글
comment-user-thumbnail
2024년 5월 9일

좋은 글 잘봤습니다!

1개의 답글