웹성능 최적화(2)

웹성능 최적화2

2022-01-03

유동균님의 강의를 보며 정리한 글 입니다.


1. 이미지 지연(lazy) 로딩

post image

예제에서 네트워크를 6메가비트로 맞춰놓았다.

제일 먼저 사용자에게 보여져야할 동영상이 이미지 보다 더 늦게 다운로드 되는 부분을 동영상이 먼저 다운로드 되도록 수정해야한다.

두번째 방법을 사용해 수정(image lazy)

이미지를 필요할때(나중에, 보여지기 직전에) 로드되도록 해야한다.

스크롤이 이미지가 있는 곳에 도달하면 이미지를 로드하고, 그렇지 않으면 보이지 않도록 해야하는데,

이 방법은 사용자가 매번 스크롤을 할 때마다 이벤트함수가 호출되는 단점이 있다.

이 문제를 해결하려면 IntersectionObserver로 해결할 수 있다. 즉 화면에 특정 이미지가 들어올 때만 함수를 호출하게 된다.

https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API

const observer = new IntersectionObserver(callback, options)
observer.observe(element객체)
    const imgRef = useRef(null)

   useEffect(() => {
      const options = {}
      const callback = () => {
         console.log('callback')
      }
      const observer = new IntersectionObserver(callback, options)

      observer.observe(imgRef.current)
   }, [])


   return (
      <div className="Card text-center">
         <img src={props.image} ref={imgRef} />
         <div className="p-5 font-semibold text-gray-700 text-xl md:text-lg lg:text-xl keep-all">
            {props.children}
         </div>
      </div>
   )
}
post image

스크롤을 내려 이미지가 보이거나 사라질때 콘솔이 찍힌다.

이미지가 보이는 그 순간에만 로드하려면 callback 함수에 entries와 observer객체를 넘겨주면 된다.

useEffect(() => {
   const options = {}
   const callback = (entries, observer) => {
      entries.forEach(entry => {
         if(entry.isIntersecting){
            console.log('is Intersecting')
         }
      })
   }
   const observer = new IntersectionObserver(callback, options)

   observer.observe(imgRef.current)
}, [])
post image

위와는 다르게 화면안에 이미지가 보일 때만 콘솔이 찍힌다.

    const imgRef = useRef(null)

    useEffect(() => {
        const options = {}
        const callback = (entries, observer) => {
            entries.forEach(entry => {
                if(entry.isIntersecting){
                    console.log('is Intersecting', entry.target.dataset.src)
                    entry.target.src = entry.target.dataset.src
                    // 이미지가 들어오면 더이상 감시하지 않음
                    observer.unobserve(entry.target)
                }
            })
        }
        const observer = new IntersectionObserver(callback, options)
        // 이미지를 넣음
        observer.observe(imgRef.current)
    }, [])


    return (
        <div className="Card text-center">
            <img data-src={props.image} ref={imgRef}/>
            <div className="p-5 font-semibold text-gray-700 text-xl md:text-lg lg:text-xl keep-all">
                {props.children}
            </div>
        </div>
    )
}
post image

동영상을 먼저 로드하고, 이미지를 불러옴


2. 이미지 사이즈 최적화

위에서 지연로딩을 사용해 이미지를 불러오는 타이밍을 조절했지만 그래도 이미지 자체의 용량이 크다면 불러오는 속도가 느려질 수 밖에 없다.

이미지 포맷, 확장자 종류

picture 태그로 이미지 분기(나중에 자세하게 배워보자)

<picture>
   <source  data-srcset={props.webp} type='image/webp'/>
   <img data-src={props.image} ref={imgRef}/>
</picture>

이미지 포맷은 https://squoosh.app/ 를 사용했다.

post image

예제에서 브라우저에 보일 크기는 300 x 300 이므로 x2를해 600 x 600으로 포맷

이미지가 9.74MB에서 21.9KB로 줄었다.


3. 동영상 사이즈 최적화

동영상이 메인 컨텐츠가 아닌 경우 적합

동영상도 WEBM으로 포맷

<video
   className="absolute translateX--1/2 h-screen max-w-none min-w-screen -z-1 bg-black min-w-full min-h-screen"
   autoPlay
   loop
   muted
>
   <source src={video_webm} type='video/webm' />
   <source src={video} type='video/mp4' />
</video>

4. 폰트 최적화

폰트도 리소스라 네트워크를 통해 받아온다.

FOUT(Flash of Unstyled Text) = 폰트를 다운로드 하기 전에는 기본폰트로 컨텐츠를 보여줌

FOIT(Flash of Invisible Text) = 폰트가 다운로드 되기 전에는 컨텐츠를 보여주지 않음

  1. 폰트 적용 시점 컨트롤
@font-face {
	font-family: BMYEONSUNG;
	src: url('./assets/fonts/BMYEONSUNG.ttf');
	font-display: swap;
}
post image
@font-face {
   font-family: BMYEONSUNG;
   src: url('./assets/fonts/BMYEONSUNG.ttf');
   font-display: block;
}
const [isFontLoaded, setIsFontLoaded] = useState(false)

const font = new FontFaceObserver('BMYEONSUNG');

useEffect(() => {
   font.load().then(function () {
      console.log('BMYEONSUNG has loaded');
      setIsFontLoaded(true)
   });
}, [])

return (
    <div className="..." style={{opacity: isFontLoaded ? 1 : 0, transition: 'opacity 0.3s ease'}} >
        <div className="...">
            {...}
        </div>
    </div>
)
  1. 폰트 사이즈 줄이기

폰트 포멧 사이트 https://transfonter.org/

@font-face {
   font-family: BMYEONSUNG;
       /* 로컬에 폰트가 있으면 바로 적용*/
   src: local('BMYEONSUNG'),
       url('./assets/fonts/BMYEONSUNG.woff2') format('woff2'),
       /* 지원하지 않는 브라우저 대응 */
       url('./assets/fonts/BMYEONSUNG.woff') format('woff'),
       url('./assets/fonts/BMYEONSUNG.ttf') format('truetype');
   font-display: block;
}

필요한 글자만 가져와서 사용 "ABCDEFGHIJKLMNOPQR"

post image

변환하지 않은 폰트는 포함되지 않음

@font-face {
   font-family: BMYEONSUNG;
        /* 로컬에 폰트가 있으면 바로 적용*/
   src: url('./assets/fonts/subset-BMYEONSUNG.woff2') format('woff2'),
        /* 지원하지 않는 브라우저 대응 */
        url('./assets/fonts/subset-BMYEONSUNG.woff') format('woff'),
        url('./assets/fonts/BMYEONSUNG.ttf') format('truetype');
   font-display: block;
   unicode-range: U+0041;
}
post image

Subset은 렌더링 하는 텍스트에 폰트가 필요하지 않아도 폰트를 로드하지만 unicode-range를 사용하면 폰트가 필요하지 않으면 로드하지 않는다

Base64 encode 해 불러오는 방식

해당하는 페이지에 폰트가 필요하다는 것을 HTML에 작성

<link rel="preload" href="BMYEONSUNG.woff2" as="font" type="font/woff2" crossorigin>
post image

CSS가 로드되기 전에 폰트가 먼저 로드됨


5. 캐시 최적화

웹 브라우저는 크게 메모리 캐시, 디스크 캐시 두가지로 캐싱한다.

메모리 캐시는 RAM에 저장, 디스크 캐시는 file로 데이터를 저장

브라우저 이미지 요청 -> 서버

서버 -> 브라우저에 이미지(캐시) 보냄

const header = {
    setHeaders: (res, path) => {
    	if(path.endsWith('.html')){
        	res.setHeader('Cache-Control', no-cache)
        } else if(path.endsWith('.js') || path.endsWith('css') || path.endsWith('.webp')){
        	res.setHeader('Cache-Control', 'public, max-age=...'
        } else {
        	res.setHeader('Cache-Control', 'no-store')
        }
    },
}