웹 성능 최적화 - Part.2

박정호·2023년 1월 11일
post-thumbnail

✔️ 캐싱 최적화

캐싱은 기본 데이터 저장소 앞에 데이터 복사본을 저장하는 전략이다. 캐싱의 장점에는 더 빠른 응답 시간과 데이터를 빠르게 제공하여 사용자 경험을 향상시키는 기능이 있다. 캐시 저장소는 일반적으로 기본 저장소보다 사용하는 클라이언트에 더 가깝다.

캐싱은 클라이언트 인스턴스가 동일한 데이터를 반복해서 읽을 때 특히 다음 조건이 모두 원래 데이터 저장소에 적용되는 경우에 가장 효과적이다.

  • 상대적으로 정적 상태로 유지
  • 캐시 속도와 비교하여 느리다.
  • 높은 수준의 경합이 발생하기 쉽다.
  • 네트워크 대기 시간이 액세스를 느려지게 할 수 있는 경우 멀리 있다.

캐싱은 성능, 확장성 및 가용성을 크게 향상할 수 있다

보유한 데이터가 많을수록 그리고 이 데이터에 액세스해야 하는 사용자 수가 많을수록 캐싱의 이점은 더 커진다. 이는 캐싱이 원래 데이터 저장소의 많은 양의 동시 요청 처리와 관련된 대기 시간 및 경합을 줄이기 때문이다.

💡 자세히 알아보자!
👉 캐시 최적화 ( Cache-Control)
👉 웹 서비스 캐시 똑똑하게 다루기



✔️ 이미지 Preload

웹사이트에는 수많은 이미지가 존재하기 때문에 웹페이지 무거워질 수 밖에 없다. 그에 대한 ㅐ결책은 앞서 보았듯 이미지의 크기, 질 등을 최소화하는 것도 있지만, 다른 방법 중 하나가 이미지 Preloading이다.

즉, 미리 로딩하고 캐시로 가져오게 하는 방식이다.

function preloading (imageArray) {
    let n = imageArray.length;
    for (let i = 0; i < n; i++) {
        let img = new Image();
        img.src = imageArray[i];
    }
}

preloading([
    '1.png',
    '2.png',
    '3.png'
])

위와같은 방법으로 Preloading을 구현할 수 있다. 그러면 개발자 도구를 확인해보면 미리 로딩할 이미지들이 로드되는 것을 확인할 수 있고 비동기로 동작하기 때문에 비슷한 시간에 모든 이미지가 로딩이 시작되는 것을 확인할 수 있다.

하지만, 우선순위가 높은 이미지가 보이지 않을 수도 있다. 따라서, 동기적으로 동작해야하는 경우에도 있을 것이다.

결국 사용자 경험,상황,목적 등에 따라 어떠한 방식으로 Preloading할 것인지 선택해야할 것이다.

💡 자세히 알아보자!
👉 이미지 불러오기



✔️ 컴포넌트 Preload

예를 하나 들어보자.

버튼을 클릭하면 모달창이 출력되는 최초페이지가 있다. 처음엔 모달 관련 파일들을 다운로드 받지 않고 모달이 보여질 때 해당 리소스를 서버로 받아오려고 한다.

물론 버튼을 클릭하지 않아 모달에 대한 리소스가 필요없을 수 있으니 최적화가 된 것은 맞다.

하지만, 모달창을 눌렀다면? 그리고 모달창에 대한 컴포넌트가 복잡하고 리소스 용량이 컸다면 오히려 사용자 경험이 좋지 않을 수 있다.


그럼 언제 컴포넌트를 preloading하여 해결할 수 있을까?

  • 버튼 위에 마우스를 올려놨을 때

  • 최초 페이지 로드가 되고, 모든 컴포넌트의 마운트가 끝났을 때

💡 자세히 알아보자!
👉 성능 최적화하기 - (5) 프리로딩(Preloading)



✔️ Image Lazy Load

Image Lazy Loading은 페이지 안에 있는 실제 이미지들이 실제로 화면에 보여질 필요가 있을 때 로딩될 수 있도록 하는 테크닉이다.

웹 페이지 내에서 바로 로딩을 하지 않고 로딩 시점을 뒤로 미루는 것이라고 할 수 있는데, 이 방식은 웹 성능과 디바이스 내 리소스 활용도 증가와 연관된 내용을 줄이는데 도움이 된다.

쉽게 말하여, 만약 사용자가 스크롤하여 페이지 밑의 화면의 이미지까지 보여줄 일이 없다면 꼭 이미지 전체를 다 로드해야할까?

성능 향상

Lazy Loading을 이용하면, 페이지 초기 로딩시 필요로 하는 이미지 수를 줄일 수 있다. 따라서 리소스 요청을 줄여 다운로드 양을 줄일 수 있고, 리소스는 더 빠르게 처리되어 다운로드하도록 확보될 것이고, 페이지를 훨씬 빨리 사용자에게 제공할 수 있다.

비용 감소

네트워크로부터 전송될 바이트 감소는 전송 비용을 줄여준다.


🧐 그렇다면 어떤 이미지에 Lazy Loading을 적용할 수 있나?

lazy loading에 어떤 이미지가 적합한지, 그리고 페이지 초기 로딩 시 얼마나 바이트 용량을 줄일 수 있는지 Google Lighthouse audit tool을 이용해서 알 수 있다

또한 ImageKit's website analyzer는 lazy loading 사용 여부를 확인할 수 있을 뿐 아니라, 페이지 내에서 크리티컬한 이미지 관련해서 최적화 용도로도 사용할 수다.

💡 자세히 알아보자!
👉 웹 성능 최적화를 위한 Image Lazy Loading 기법



✔️ 컴포넌트 Lazy Load

Lazy Loading을 통해서 최적화 작업을 진행할 수 있다.

지연로딩은 로딩을 바로 하지 않고 지연시켰다가 나중에 로딩하게 해준다는 뜻

사용자가 사이트에 접속했을 때 보이지 않는 것까지 모두 로드해오는 것이아니라 보이는 페이지만 로드한 후 다른 페이지에 접속했을 때 그 곳의 데이터를 로드해오는 작업 해주는 것이다.

Suspense

Lazy 컴포넌트는 Suspense 컴포넌트 하위에서 렌더링되기 때문에 Suspense 컴포넌트로 lazy 컴포넌트를 감싸고, Suspense는 Lazy컴포넌트가 로드되길 기도리는 동안 spinner 또는 shimmer 같은 예비 컨텐츠를 보여줄 수 있다. fallback prop으로 컴포넌트가 로드될때까지 기다리는 동안 스피너를 적용시켜 보여줄 수 있게 하는 것이다.

실제 적용 코드

// layout.tsx
const Layout: React.FC = () => {
  return (
    <div>
      <Suspense fallback={<Loading />}>
        <Outlet />
      </Suspense>
    </div>
  );
};

// routes.tsx
const DynamicIndex = React.lazy(() => import('./pages/index'));
const DynamicAdminIndex = React.lazy(() => import('./pages/admin/index'));
const DynamicCartIndex = React.lazy(() => import('./pages/cart/index'));
const DynamicPaymentIndex = React.lazy(() => import('./pages/payment/index'));
const DynamicProductsIndex = React.lazy(() => import('./pages/products/index'));
const DynamicProductsId = React.lazy(() => import('./pages/products/[id]'));

Lazy-loading의 단점
페이지 내부의 Modal 컴포넌트와 같은 요소에 Lazy-loading을 적용한다면, 사용자가 Modal을 클릭한 후 화면에 렌더되기 전까지 딜레이가 발생해 UX적으로 악영향을 끼친다.

즉, 컴포넌트를 지연로딩을 하면 불러오기까지 딜레이가 발생한다. 리소스를 요청하고 다운받은 후에 평가가 되야지 컴포넌트가 보여진다.

이러한 딜레이를 줄이기 위해서는 미리 컴포넌트를 불러오면 해결할수 있다.(앞서 본 Preload)

💡 자세히 알아보자!
👉 [React] 리액트 lazy loading 적용하기(lighthouse 성능 최적화)



✔️ React Code Splitting

번들링 된 앱은 최적화된 것이지만 앱이 커질 수록 번들도 커지기 마련이다. 따라서 번들이 거대해지는 것을 방지하기 위한 좋은 해결방안이 코드 분할이다.

코드분할은 런타임에 여러 번들을 동적으로 만들고 불러오는 것으로 Webpack, Rollup 같은 번들러가 지원하는 기능이다.

💡 코드 분할은 큰 파일의 코드를 더 작은 코드 번들로 분할하는 방법.

코드분할은 앱을 Lazy Loading 할수 있게 도와주고 성능의 향상으로 이끈다.

dynamic import()

  • Webpack이 이 구문을 만나게 되면 앱의 코드를 분할한다. Create React App을 사용하고 있다면 이미 Webpack이 구성 되어 있기 때문에 자동으로 실행될 것임다.
// Before
import { add } from './math';

console.log(add(16, 26))

// After
import("./math").then(math => {
  console.log(math.add(16, 26));
});

React.lazy

  • React.lazy 함수를 사용하면 dynamic import를 사용해서 컴포넌트를 렌더링 가능
// Before
import OtherComponent from './OtherComponent';

// After
const OtherComponent = React.lazy(() => import('./OtherComponent'));

💡 자세히 알아보자!
👉 React에서 해보는 코드 스플리팅 (Code Splitting)
👉 리액트 Code-splitting 실적용 사례
👉 [React] 코드 분할(Code Splitting) - React 더 잘 사용하기



✔️ 애니메이션 최적화 (Reflow, Repaint)

💡 자세히 알아보자!
👉 애니메이션 최적화 (Reflow, Repainting)
👉 Reflow or Repaint(or ReDraw)과정 설명 및 최적화 방법
👉 [JavaScript] 렌더링 최적화 (Reflow와 Repaint)



profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글