swr에서 prefetch를 사용하고, 얻은 이미지를 preload 하는 방법에 관한 글입니다.
사이트를 들어가면, 첫 렌더링에 로딩이 상당히 오래걸리는 사이트들이 몇 있습니다. js, css, html 불러오는데 그렇게 오래걸릴리는 없잖아요
대부분 이러한 로딩은 이미지, 비디오, 폰트 처럼 크기가 큰 assets 을 로딩하는데 걸리는 시간입니다.
이 글에서는 react에서 어떻게 이미지를 prefetch 하고 preload 하는지 알아보겠습니다.
저는 캐싱 라이브러리로 SWR을 자주 사용하는데, SWR 은 prefetch
기능을 지원합니다.
두가지 방법이 있는데, index.html
에서 로드하는 방법과, 코드로 로드하는 방법이 있습니다.
<link rel="preload" href="/api/data" as="fetch" crossorigin="anonymous">
index.html
파일에 위 한줄만 추가하면, href 의 경로로 미리 데이터를 fetch
합니다.
import useSWR, { preload } from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json())
// You can call the preload function in anywhere
preload('/api/user', fetcher)
function Profile() {
// The component that actually uses the data:
const { data, error } = useSWR('/api/user', fetcher)
// ...
}
export function App () {
return <Profile/>
}
이렇게 실행하면 App.jsx
가 렌더링 되기 이전에 preload
가 실행됩니다. next.js
의 서버사이드 렌더링과 비슷한 느낌으로 생각하면 좋을 것 같습니다.
위와 같이 데이터를 prefetch 해왔을 때, 이미지 파일이 있을 수 있습니다.
이미지는 대부분 url
로 저장되어 있기 때문에, 브라우저에서 다운로드 받는 과정이 필요합니다. 따라서 swr로 prefetch 해왔다고 해도, 이미지가 바로 보이지 않을 확률이 높습니다
prefetch 해온 값은 이미지url 값이지 이미지가 아니기 때문입니다.
import { useEffect, useState } from "react";
function preloadImage(src: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function () {
resolve(img);
};
img.onerror = img.onabort = function () {
reject(src);
};
img.src = src;
});
}
export default function useImagePreloader(imageList: string[]) {
const [imagesPreloaded, setImagesPreloaded] = useState<boolean>(false);
useEffect(() => {
let isCancelled = false;
async function effect() {
if (isCancelled) {
return;
}
const imagesPromiseList: Promise<any>[] = [];
for (const i of imageList) {
imagesPromiseList.push(preloadImage(i));
}
await Promise.all(imagesPromiseList);
if (isCancelled) {
return;
}
setImagesPreloaded(true);
}
effect();
return () => {
isCancelled = true;
};
}, [imageList]);
return { imagesPreloaded };
}
useImagePreloader
커스텀 훅은
이미지url 의 list 를 인자로 받고, 로드한 후 true, 로드전에는 false 를 반환합니다.
import React, { useEffect, useState } from 'react'
import useImagePreloader from 'hooks/useImagePreloader'
const preloadSrcList = [
"image-url-1",
"image-url-2",
"image-url-3",
]
export default function Component() {
const { imagesPreloaded } = useImagePreloader(preloadSrcList)
if (!imagesPreloaded) {
return <p>Preloading Assets</p>
}
return <p>Assets Finished Preloading</p>
}
const fetcher = (url: string) => fetch(url).then((res) => res.json());
const projects = await preload(getProjectsApi, fetcher);
function App() {
const { imagesPreloaded } = useImagePreloader(
projects.map((p: any) => p.thumbImageUri)
);
return (
<>
{loading || !imagesPreloaded ? (
"loading..."
) : (
<div>
{projects.map((project) => (
<img src={project.thumbImageUri} />
))}
</div>
)}
</>
);
}
SWR 의 preload 를 사용해서, 먼저 fetch 해온후, 그 값중 이미지 url 을 미리 로드한 후, 로드되었다면 렌더링 하는 코드입니다.