웹뷰 최적화 만점을 위한 노력 -2-(IntersectionObserver, Token provider, Prefetch, font, Lcp, Lighthouse)

김규빈·2022년 12월 9일
32
post-thumbnail

개선 후

개선 전

만점에 가까운 라이트 하우스 점수 쨖쨖 👏

어니언은 앱 서비스이지만, 웹의 장점이 필요한 페이지는 vue를 이용하여 웹으로 구성되어 있다.
그렇기에 앱에서 웹뷰 페이지를 로드할 경우 이질감 없이 로드하여 서비스를 제공하여야 하기 때문에 단일 페이지 구성으로 초기 로드 속도와 앱, 웹 통신이 가능해야 하고, 앱과 일관된 페이지 구성을 해야만 사용자 경험을 해치지 않고 서비스를 제공할 수 있다.
또한 컴퓨터보다는 성능이 떨어지는(구형 디바이스)에 대한 대응이 필요하기 때문에 최적화에 신경을 많이 써야 한다.
웹뷰 성능 개선을 위해 적용한 내용을 소개해 보겠다.

1. IntersectionObserver + keyframes

최적화 전 애니메이션은 라이브러리를 통해 구현했었는데 aos + gsap를 통해 구현했었다.
거의 모든 페이지에 스크롤 애니메이션이 필요했는데, 많은 페이지의 라이브러리 의존성 리스크와 더 이상 관리하지 않는 라이브러리 + 번들 사이즈를 최대한 줄이고자 모두 걷어내고 IntersectionObserver를 도입했다.
스크롤 + 애니메이션이 필요하다면 최고의 조합이라고 생각하는데, 간단하게 말하자면 IntersectionObserver를 통해 애니메이션 영역을 Observe 하게 만들고 스크롤 하여 viewport 영역으로 보이면 callback을 통해 객체로 전달해 준다. 전달받은 객체에 keyframes 애니메이션을 바인딩 해주면 자연스러운 스크롤 애니메이션 적용이 가능하다.
IntersectionObserver 같은 경우는 rootMargin, threshold 같은 개발자에게 공수를 줄여주는 옵션들도 제공하고 있고 여러 성능 검증을 통해 우월한 퍼포먼스를 보여준다고 검증되어 있기 때문에 도입을 적극 추천한다.

예시코드

<script>
export default {
  props: ["type", "delay", "duration"],
  data() {
    return {
      observer: null,
      option: {
        rootMargin: "50px",
        delay: 300,
      },
    };
  },
  render() {
    return this.$slots.default;
  },
  methods: {
    handleIntersect(target) {
      const element = target.target;

      element.classList.add("before-enter");

      if (target.isIntersecting) {
        element.classList.add("after-enter");
        this.delay ? (element.style.transitionDelay = this.delay + "ms") : null;
        this.duration ? (element.style.transitionDuration = this.duration + "ms") : null;
        element.classList.add(this.type);
      } else {
        element.classList.remove("after-enter");
        element.classList.remove(this.type);
      }
    },
  },
  mounted() {
    this.observer = new IntersectionObserver((entries) => {
      this.handleIntersect(entries[0]);
    }, this.option);

    this.observer.observe(this.$el);
  },
  destroyed() {
    this.observer.disconnect();
  },
};
</script>

이렇게 observer컴포넌트를 만들고, 사용하고자 하는 영역을 감싸 keyframes 애니메이션을 바인딩 해주면서 애니메이션을 구현하였다

2. Token provider

앱과 웹 사이의 소통이 필수적으로 필요한데, 예를 들자면 유저 정보나 컨텐츠 참여 유무 등이 있을 것이다. 기존엔 페이지 내부에서 통신을 했다면

<TokenProvider>
    <router-view :key="$route.fullPath"></router-view>
 </TokenProvider>

이런식으로 provider 패턴을 통해 TokenProvider 위임함으로써 의존성을 분리하고 페이지 내부에서 보다 빠른 통신이 가능해졌다.

3. Prefetch

Single Page Application(SPA)의 고질적인 문제는 소스코드가 하나로 뭉쳐져서 사용자가 처음 웹사이트에 접속했을 때 큰 파일을 다운로드하고 파싱을 하느라 초기 렌더링이 느려진다는 점이다. prefetch는 미래에 사용될 수 있는 리소스를 미리 캐시 해 두기 때문에, 매우 유용한 기능이지만 웹뷰의 경우 단일 페이지 로드가 대부분이었기 때문에 빠른 초기 로드가 좀 더 필요한 상황이었다. 그리하여 prefetch 기능을 제거하고 페이지별 우선순위로 필요한 리소스를 가져오게 변경함으로써 초기 로드를 앞당겼다. prefetch를 적용하면

리소스를 모두 가져와 캐시하기 때문에 59개의 request가 생기고 로드가 늦어질 수 밖에 없다.


prefetch 기능을 제거한다면 페이지에 필요한 리소스만 request를 하기 때문에 좀 더 빠른 로드가 가능하다. 하지만 캐시를 하여 이점을 챙길 수 있는 구조도 있기 때문에 특정 컴포넌트에만 prefetch를 통해 적절하게 판단하여 적용하는 게 중요하겠다 (예를 들면 라우팅하는 페이지)

import(/* webpackPrefetch: true */ './views/About.vue');

4. font preload

CRP 높일 수 있는 방법 중 하나인 폰트 preload다. 위 사진에서 폰트가 불러오기 전 대체 폰트로 로드 했다가 불러온 폰트로 변하는 게 보이는가. 폰트가 달라지면서 울컥 울컥 한 장면이 나오는데 이를 해결할 방법은 preload로 폰트 로드를 우선순위를 주어 먼저 로드 시키는 방법이다.
웹사이트를 파싱 하는 과정은 알고 있다고 가정하에 폰트를 먼저 로드 함으로써 이후에 콘텐츠를 로드를 할 때 먼저 불러온 폰트를 적용하여 초기 로드 속도를 높일 수 있다.
흔히 @font-face를 적용하여 css에서 폰트 url를 가져와 로드 시키는 방법을 사용하는데, 이 방법보다는

<link rel="preload" as="font" type="font/otf" href="/src/assets/font/SpoqaHanSansNeo-Bold.otf"  crossorigin="anonymous">

head에 preload를 적용시키고 font-family를 통해 적용하는 편이 초기 로드 속도에 유리하다.

이렇게 우선적으로 폰트를 파싱한다.

6.Light house

필자는 라이트 하우스로 퍼포먼스 측정을 했는데, fcp와 lcp를 추적하면서 최적화를 진행했다. 라이트 하우스에선 카테고리 별로 성능을 떨어트리는 요소를 꽤나 구체적으로 알려주고 과도한 이미지 사이즈 요소, 이미지 요소에 넓이 높이 값을 주어지지 않아 Reflow 과정에 부담을 주는 요소, seo 점수를 위한 웹 표준을 준수하지 않는 요소 등 최적화에 필요한 도움을 주기 때문에 꼭 성능 측정을 해보길 추천한다. (best practice는 최대한 충족하도록 해보자)

결론

항상 높은 퍼포먼스와 클린 아키텍쳐를 염두해 두는 근사한 fe가 되도록 노력하자.

이 부분은 나중에 수정해야지 => 나중은 오지 않음.
우선은 이렇게 가고 더 좋은 방향성을 나중에 생각해보자 => 생각 안해본다.
에이 그렇게 까지 바뀐다고? => 높은 확률로 바뀐다.
리펙토링때 하자 => 리펙토링때 할거 (0/50) 나중엔 기억도 안남.
지금 바쁜데 이렇게까지 확장성을 염두 해야 되나? => 나중에 싱글벙글 갖다 씀.

profile
FrontEnd Developer

4개의 댓글

comment-user-thumbnail
2022년 12월 9일

잘봤습니다 감사합니다.

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

마지막 부분이 굉장히 공감가네요! 감사합니다

1개의 답글