물리적 거리의 한계를 극복하기 위해 소비자(사용자)와 가까운 곳에 컨텐츠 서버를 두는 기술
이미지 CDN 프로세스를 활용해서 받아올 이미지 사이즈를 미리 줄여서 받아올 수 있도록 한다.
대표적으로 imgix 같은 서비스를 활용하는 방법도 있고 아래와 같은 함수로 조정하는 방식도 존재한다. 하지만 아래와 같은 방식으로 적용시킬려면 서버와 함께 어떤 옵션을 추가할지를 정의해야하는 사전 작업이 필요하다.
/* 파라미터 참고: https://unsplash.com/documentation#supported-parameters */
function getParametersForUnsplash({width, height, quality, format}) {
return `?w=${width}&h=${height}&q=${quality}&fm=${format}&fit=crop`
}
버틀넥(병목현상) 이란 엄청난 양의 데이터를 순식간에 처리하다보니 메모리가 이를 제대로 소화하지 못해 성능 저하를 나타내는 현상이다.
let _str = _str.replace(/\#\_\*\~\&\;\!\[\]\n\=\-/g, "");
let _str = str.substring(0,300)
번들 사이즈는 페이지의 초기 로딩 속도에 큰 영향을 끼친다. 그러므로 번들 사이즈를 줄이면 초기 로딩시간을 개선시킬 수 있다.
Code Splitting은 덩치가 큰 번들 파일을 쪼개서 코드를 분할 하는 것을 의미한다.
불필요한 코드 또는 중복되는 코드가 없이 적절한 사이즈의 코드가 적절한 타이밍에 로드될 수 있도록 하는것이 중요하다.
리액트의 코드 분할 방법들에 대해서는 리액트 공식 문서에서 찾아 볼 수 있다.
기본적으로 code splitting을 적용할려면 webpack 설정이 되어있어야 하지만 Cra, Cna로 생성된 프로젝트는 자동으로 옵션이 적용이 되어 있다.
라우트 기반 코드 스플릿팅은 기본적으로 react에서 제공되고 있는 lazy 로딩과 Suspense를 사용해서 할 수 있다.
const ListPage = lazy(() => import("./pages/ListPage/index"));
const ViewPage = lazy(() => import("./pages/ViewPage/index"));
function App() {
return (
<div className="App">
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" component={ListPage} exact />
<Route path="/view/:id" component={ViewPage} exact />
</Switch>
</Suspense>
</div>
);
}
적용된 코드를 네트워크 탭을 활용해서 확인 해보면 결과는 아래와 같다.
localhost:3000/
localhost:3000/view/20
해당 url을 접속했을 때야 비로소 1.chunck.js라는 용량이 큰 파일을 불러오는 것을 볼 수 있다. 이처럼 라우트 상태에 따라 큰 용량을 차지하는 모듈을 필요할떄만 불러와서 사용 할 수 있다.
번들 어날라이저를 통해 확인해보면 code splitting을 진행하기 전과 구조가 변한 것을 확인할 수 있다.
애니메이션을 분석하기 위해서는 Reflow 와 Repain가 브라우저에서 어떻게 이루어지고 있는지를 알아야한다.
먼저 브라우저 렌더링 과정에 대해 간략하게 정리해보자.
DOM + CSSOM 생성 => Render Tree 생성 => Layout진행 => Paint진행 => Composite 과정으로 이루어져있다. 이 모든 과정을 Critical Rendering Path 아니면 Pixel Pipeline이라고 칭한다.
브라우저의 렌더링 과정을 배워보았다. 브라우저는 화면에 변화가 있을때마다 위와 같은 과정을 반복적으로 실행하여 새로운 화면을 그리게 되는데 그중 제일 성능적인 부분을 많이 차지하는게 Layout과 Paint하는 과정이다. 새로운 화면을 그리는 과정에서 이 두가지의 과정을 생략할 수 있게 된다면 성능적으로 크게 개선할 수 있게 된다.
Layout 과정을 다시 진행하는걸 Reflow라고 한다. 요소의 위치나 크기를 변화 시키는 css가 변했을 시 Reflow가 발생하는 원리이다. 아래와 같은 속성들을 변경하는걸 최대한 지향하면 Reflow가 발생하지 않기 떄문에 사용을 줄여주는게 좋다.
Layout에 색을 다시 채우는 과정을 Repaint라고 한다. 만약 색을 채우는 과정인 paint 속성에만 변화를 줬을 경우 Reflow과정을 생략하고 Repaint를 진행 할 수 있다.
transform, opacity는 웹브라우저의 GPU가 관여할 수 있는 속성이기에 Relfow 및 Repaint 과정을 생략할 수 있는 방법이다. 그리하여 transform 및 opacity를 다른 속성을 대신해서 사용할 수 있는 경우라면 적극적으로 사용해주자.
위와 같은 Progressbar를 최적화해보도록 하자.
최적화 전 코드를 보면 width라는 props를 받아서 width를 조정해서 reflow를 발생시켜 animation을 보여주고 있다. 이와 같은 방법은 렌더링 과정을 처음부터 끝까지 다시 진행하므로 성능적인 부분에 영향을 미치게 된다. 좀 더 복잡한 애니메이션이 들어가게 될 경우 사용자 입장에서도 버벅임을 보이게 될 수 있다.
width: ${({width}) => width}%;
transition: width 1.5s ease;
Reflow와 Repaint를 생략할수 있는 transform 속성을 활용해서 아래와 같이 최적화를 했다.
width: 100%;
transform: scaleX(${({ width }) => width / 100});
transform-origin: center left;
transition: transform 1.5s ease;
Preloading 기법은 화면에 보여줘야할 용량이 큰 파일이나 컴포넌트를 미리 로딩시켜놓고 필요할 떄 보여줄수 있도록하여 로딩 지연시간을 단축시켜주는 기법이다. 위에서 배운 Code Splitting 기법과 조합하여 적절하게 사용하면더 좋은 성능 최적화를 할 수 있다.
기본적으로 컴포넌트를 preloading하는 기법은 두 가지가 있다. 두 가지 기법을 상황에 따라 적절하게 사용하면 된다.
function lazyWithPreload(importFunction) {
const Component = React.lazy(importFunction);
Component.preload = importFunction;
return Component;
}
function App(){
const handleMouseEnter = () => {
LazyImageModal.preload();
};
return (
<ButtonModal
onClick={() => {
setShowModal(true);
}}
onMouseEnter={handleMouseEnter}
>
올림픽 사진 보기
</ButtonModal>
)
}
useEffect(() => {
LazyImageModal.preload();
}, []);
이미지를 Preloading하는 방법은 new Image() 객체를 미리 생성해 놓는 방법이다. 이미지 프리로딩 같은 경우에는 꼭 첫화면에 제일 먼저 보여져야할 이미지만 해주는 것이 좋다. 예를 들어 모달창을 띄었을 때 보이는 첫번째 이미지만 프리로딩하지 않고 모달창에 들어가는 모든 이미지를 preloading 해놓는건 오히려 성능에 악영향을 끼칠 수 있다.
useEffect(() => {
const img = new Image();
img.src =
"https://stillmed.olympic.org/media/Photos/2016/08/20/part-1/20-08-2016-Football-Men-01.jpg?interpolation=lanczos-none&resize=*:800";
}, []);