프론트 성능 개선 프로젝트를 하면서 배운점

내가 다니고 있는 회사는 커머스 쇼핑몰 사이트이다보니, 폰트나 이미지 리소스가 많고, 많은 API를 호출하고 있어서 웹페이지 로드에 리소스 요청이 사이트의 성능에 많은 영향을 끼치고 있다. 그러면서 요청받았던 프론트 성능 개선 프로젝트를 진행하면서 주로 이미지와 폰트 관련해서 알게된 내용을 적어본다.

요구사항

  1. 보이는 부분의 이미지를 먼저 로드해주세요.
  2. 이미지가 먼저 렌더링되고 나서 폰트가 렌더링되게 해주세요.

해결방안

  1. 최소한의 리소스와 최소한의 리퀘스트
  2. 이미지 최적화
    1. 필요한 이미지만 로딩한다.
    2. 캐싱 전략
  3. 글꼴 최적화
    1. 필요한 글꼴만 로딩한다.
    2. 캐싱 전략

이미지 최적화하기

이미지 압축

IntersectionObserverService 사용하기

IntersectionObserver API를 이용하여 보이는 부분(뷰포트에 교차되는 지점)의 이미지만 로딩한다.

  1. IntersectionObserverService
     import { Injectable } from '@angular/core';
     import 'intersection-observer';
    
     declare global {
     interface Window {
         IntersectionObserver: IntersectionObserver;
     }
     }
    
     const defaults = {
     root: null,
     rootMargin: '0px 0px 0px 0px',
     threshold: [0, 0.25, 0.5, 0.75, 1],
     };
    
     @Injectable()
     export class IntersectionObserverService {
     observer: IntersectionObserver;
    
     constructor(
         callback: IntersectionObserverCallback,
         options: IntersectionObserverInit = defaults
     ) {
         if (window.IntersectionObserver) {
         this.observer = new IntersectionObserver(callback, options);
         }
     }
     }
    

    https://gist.github.com/xx4159/12277c7ca840b6a326e399f689f2d716

  2. 교차지점으로 삼을 root 엘리먼트(default: null = viewport)를 세팅한다.
     import { Component } from '@angular/core';
     import { IntersectionObserverService } from '@common/intersection-observer.service';
    
     export class AppComponent {
         // ...
         IntersectionObserverService.shared.setup();
         // ...
     }
    

Issue

  • Progress Image
    • 이미지 높이가 없어서 높이가 0으로 잡혀있다가 확 커짐. 페이지 뒤로가기로 돌아왔을 때 원래있던 스크롤 위치를 못찾음.
      • 방법1. fake element(div)를 만들어 뒀다가 이미지 다운되면 img 태그 append
      • 방법2. bLazy 라이브러리 활용
      • 방법3. 이미지에 높이를 먼저 준다
        • -> 방법2와 방법3의 방법으로는 이미지높이가 잘리거나 여백이 생길 수 있음
    • 이미지 렌더링 과정이 보인다.
      • transition 효과주기(opacity:0 -> .loaded 클래스명 추가)
    • 이미지 로딩 후 onSuccess/onError @Output 필요
  • Browser Support
    • IE 는 전혀 지원하지 않는다. polyfill 사용하기

글꼴 최적화하기

  1. 형식 선택. 웹폰트형식을 woff2 사용
    • 현재는 woff 사용중
    • woff2 사용 시 30% 정도 파일 크기를 절감한다고 함
    • 지원 브라우저: https://caniuse.com/#feat=woff2
  2. 유니코드 범위 서브세팅. 주로 사용하는 유니코드(특수문자)의 범위를 지정
    • 현재는 범위가 전체로 셋팅되어있음.
    • 아시아 언어는 문자 수가 많고, 일반적인 전체 글꼴은 대개 수십 킬로바이트가 아니라 메가바이트로 측정됨
    • 지원 브라우저: https://caniuse.com/#feat=font-unicode-range
  3. 글꼴 선택 및 합성. 글꼴 버전의 수 최소화
    • 현재는 100 부터 900까지 모든 두께의 버전을 불러옴.
    • 버전은 각각 개별적인 다운로드이므로 버전 수를 적게 유지하는 것이 좋음
  4. Font Loading API를 사용하여 글꼴 렌더링 최적화
    • 현재는 사파리에서 빈 텍스트 문제가 발생
    • Safari는 글꼴 다운로드가 완료될 때까지 텍스트 렌더링을 지연시킴
    • 지원 브라우저: https://caniuse.com/#search=font%20loading
  5. 인라인 처리를 통해 글꼴 렌더링 최적화
    • 현재는 DOM을 그리고 나서 폰트를 불러옴.
    • 인라인 처리하면 브라우저가 우선적으로 글꼴을 다운로드하도록 만들 수 있음
    • 모든 브라우저에서 동작하지만 폰트가 로딩되는 동안 브라우저는 DOM을 늦게 그리게 됨.
  6. HTTP 캐싱을 통한 글꼴 재활용 최적화
    • 현재 ETag 헤더는 적용되어 있지만 Cache-Control은 no-cache 라서 매번 서버에 유효성 검사를 다시함(서버에서 매번 다운로드는 하지않음)
    • 글꼴 리소스는 일반적으로 자주 업데이트되지 않는 정적 리소스이므로 캐시를 재활용할 수 있는 시간을 최대한 길게 가져가는 것이 좋을 듯 함.

참고 링크

  • https://github.com/codepink/codepink.github.com/wiki/%EB%84%88%EB%8A%94-%EB%82%98%EB%A5%BC-%EB%B3%B8%EB%8B%A4:-%EC%A7%80%EC%97%B0-%EB%B0%A9%EB%B2%95,-%EB%A0%88%EC%9D%B4%EC%A7%80-%EB%A1%9C%EB%93%9C%EC%99%80-IntersectionObserver%EC%9D%98-%EB%8F%99%EC%9E%91
  • https://stackoverflow.com/questions/40714934/why-does-this-request-return-a-200from-cache-yet-others-return-304?rq=1
  • https://jakearchibald.com/2016/caching-best-practices/
  • http://cyberx.tistory.com/9