성능
체감상으로도 블로그게시글을 불러오는게 느리다고 판단되어, 라이트하우스로 측정한 결과 성능이 55점이라는 처참한? 결과가 나왔다.
- 첫번째로는 초기 서버 응답 시간이 길었다. 이유는 스크롤하면 캐릭터가 움직이는 3d 모델링을 로딩하는게 가장 큰 원인이었다. 3d모델링은 gltf파일인데 무려 10,782kb나 된다.
- 두번째로는 전체 게시글 리스트를 한번에 받아와서이다. 서버에서 페이징처리를 해서 불러올까 했지만 아직 게시글이 그렇게 많지 않아서 지연로딩을 통해 개선하기로 하였다.
- 세번째로는 이미지 최적화를 하지 않아서였다. 이미지 최적화를 하지 않고 업로드한 이미지를 그대로 firebase에 업로드 하였기에, 이미지를 로드하는데도 시간이 어느정도 걸린 것 같다.
1. 웹 3D모델링(gltf파일) 최적화
https://d2.naver.com/helloworld/6152907
위 게시글에서 gltf의 최적화에 대한 내용이 다루어져있지만, 아직은 조금 어려운 내용이어서 일단은 모델링을 주석처리하여 렌더링하지 않게 하였다.
추후 적용해보게 된다면 다시 다뤄볼 예정이다.
2. 지연로딩
- intersection Observer API를 사용한 custom hook 만들기
- https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
import { useState, useEffect, RefObject } from 'react';
function useVisibility<T extends HTMLElement>(ref: RefObject<T>): boolean {
const [isVisible, setIsVisible] = useState<boolean>(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
setIsVisible(entry.isIntersecting);
},
{
root: null,
rootMargin: '0px',
threshold: 0.03, // 구성 요소가 표시되는 것으로 간주되어야 하는 시기를 제어하려면 이 값을 조정합니다.
}
);
if (ref.current) {
observer.observe(ref.current);
}
return () => {
if (ref.current) {
observer.unobserve(ref.current);
}
};
}, [ref]);
return isVisible;
}
export default useVisibility;
스크롤 이동에 따라 ref의 children(PostCard컴포넌트)이 보이는 상태가 되면 해당 PostCard컴포넌트를 렌더링하도록 수정해주었다.
3. 이미지 최적화
에디터를 통해 firebase로 업로드 시, browser-image-compression라이브러리를 사용하여 이미지를 압축하여 업로드 하였다.
const onUploadImage = async (blob:Blob, callback: (url: string, altText?: string) => void) => {
const fileName = `${Date.now().toString()}_${blob.name}`;
const storageRef = ref(storage, `images/${fileName}`);
const imageFile = new File([blob], fileName, { type: 'image/jpeg' });
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
}
try {
const compressdFile = await imageCompression(imageFile, options) // 이미지 압축
const snapshot = await uploadBytes(storageRef, compressdFile); // 이미지 업로드
const url = await getDownloadURL(snapshot.ref); // 에디터에 넣을 이미지 url
images.current = Array.isArray(images.current) ?
[...images.current, {fileName: fileName, url: url.replaceAll(/&/g, '&')}]
:
[{fileName: fileName, url: url.replaceAll(/&/g, '&')}]
// 이미지가 업로드된 후 다시 제거할 경우 최종 업로드 시 firebase에서 해당 이미지를 지워주기 위해 사용되는 배열
callback(url, 'image')
} catch (error) {
console.log('error', error)
alert('이미지 업로드 실패')
}
}
👇🏻 일부러 큰 이미지를 넣어보았고, 위쪽이 압축전 기존 코드로 올린 이미지파일이고 아래가 이미지 최적화 진행 후 업로드한 이미지파일이다. 꽤나 크기차이가 있다.
접근성
이유는 버튼과 링크에 접근 가능한 이름이 없어서인데, 버튼에 이름을 넣어주어야 브라우저 리더기가 무슨 버튼인지 읽어주므로 접근성이 높아진다.
- 버튼: 텍스트가 들어가지 않는 아이콘버튼이기에 title을 넣어주었다.
<button type="button" onClick={() => setOpenSearch(true)} title="search">
<svg className="h-4 w-4 fill-current text-gray-700 dark:text-pink-50" xmlns="http://www.w3.org/2000/svg"
version="1.1" id="Capa_1" x="0px" y="0px"
viewBox="0 0 56.966 56.966"
width="512px" height="512px">
<path
d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z" />
</svg>
</button>
- 링크: aria-label 추가
<Link href={`/detail/${post._id}`} ref={ref} aria-label={post.title}>
개선 결과
성능: 55 -> 93
접근성: 85 -> 97
버튼의 색상대비가 충분하지 않긴하지만, 간단한 개선으로 어느정도 접근성을 올렸고, 리액트로 구현을 하더라도 기본 Html태그의 기본에 충실해야함을 인지하게 되었다.