웹성능 최적화(2)
웹성능 최적화2
2022-01-03
유동균님의 강의를 보며 정리한 글 입니다.
1. 이미지 지연(lazy) 로딩
예제에서 네트워크를 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>
)
}
스크롤을 내려 이미지가 보이거나 사라질때 콘솔이 찍힌다.
이미지가 보이는 그 순간에만 로드하려면 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)
}, [])
위와는 다르게 화면안에 이미지가 보일 때만 콘솔이 찍힌다.
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>
)
}
동영상을 먼저 로드하고, 이미지를 불러옴
2. 이미지 사이즈 최적화
위에서 지연로딩을 사용해 이미지를 불러오는 타이밍을 조절했지만 그래도 이미지 자체의 용량이 크다면 불러오는 속도가 느려질 수 밖에 없다.
이미지 포맷, 확장자 종류
- JPG를 WEBP(구글에서 나온 이미지 포맷, JPG에 비해서 화질이높고 용량이 낮음)로 변경,
- 꼭 WEBP로 할필요는 없다 (WEBP, WEBM은 현재 잘 쓰지 않는다) 현재 지원하지않는 브라우저도 있음
picture 태그로 이미지 분기(나중에 자세하게 배워보자)
<picture>
<source data-srcset={props.webp} type='image/webp'/>
<img data-src={props.image} ref={imgRef}/>
</picture>
이미지 포맷은 https://squoosh.app/ 를 사용했다.
예제에서 브라우저에 보일 크기는 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) = 폰트가 다운로드 되기 전에는 컨텐츠를 보여주지 않음
- 폰트 적용 시점 컨트롤
- font-display 사용 - auto : 브라우저 기본 동작 - block : FOIT (timeout = 3s) - swap : FOUT - fallback : FOIT (timeout = 0.1s) 0.1초 후에도 불러오지 못하면 기본 폰트 유지, 이후에 캐시 - optional : FOIT (timeout = 0.1s) 이후 네트워크 상태에 따라 기본폰트로 유지할지 웹폰트를 적용할지 결정, 이후에 캐시
@font-face {
font-family: BMYEONSUNG;
src: url('./assets/fonts/BMYEONSUNG.ttf');
font-display: swap;
}
- fontfaceobserver 라이브러리를 사용해 시각적인 효과
@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>
)
- 폰트 사이즈 줄이기
폰트 포멧 사이트 https://transfonter.org/
- 웹폰트 포맷 사용 (파일 크기 = EOT > TTF/OTF > WOFF > WOFF2)
@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;
}
-
local 폰트 사용
-
Subset 사용
필요한 글자만 가져와서 사용 "ABCDEFGHIJKLMNOPQR"
변환하지 않은 폰트는 포함되지 않음
- Unicode Range 적용
@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;
}
Subset은 렌더링 하는 텍스트에 폰트가 필요하지 않아도 폰트를 로드하지만 unicode-range를 사용하면 폰트가 필요하지 않으면 로드하지 않는다
- data-uri로 변환
Base64 encode 해 불러오는 방식
- Preload
해당하는 페이지에 폰트가 필요하다는 것을 HTML에 작성
<link rel="preload" href="BMYEONSUNG.woff2" as="font" type="font/woff2" crossorigin>
CSS가 로드되기 전에 폰트가 먼저 로드됨
5. 캐시 최적화
웹 브라우저는 크게 메모리 캐시, 디스크 캐시 두가지로 캐싱한다.
메모리 캐시는 RAM에 저장, 디스크 캐시는 file로 데이터를 저장
- Cache-Control
브라우저 이미지 요청 -> 서버
서버 -> 브라우저에 이미지(캐시) 보냄
- 서버에서 설정이 필요
-
no-cache : 캐시를 사용하기 전에 서버에서 검사 후 사용 결정
-
no-store : 캐시 사용 안함
-
public : 모든 환경에서 사용
-
private : 브라우저 환경에서만 캐시 사용, 외부 캐시 서버에서는 사용 불가
-
max-age : 캐시의 유효시간
-
Node
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')
}
},
}