Preload & Prefetch 이미지

훈나무·2022년 12월 21일
1

react

목록 보기
3/3
post-thumbnail

swr에서 prefetch를 사용하고, 얻은 이미지를 preload 하는 방법에 관한 글입니다.

사이트를 들어가면, 첫 렌더링에 로딩이 상당히 오래걸리는 사이트들이 몇 있습니다. js, css, html 불러오는데 그렇게 오래걸릴리는 없잖아요

대부분 이러한 로딩은 이미지, 비디오, 폰트 처럼 크기가 큰 assets 을 로딩하는데 걸리는 시간입니다.

이 글에서는 react에서 어떻게 이미지를 prefetch 하고 preload 하는지 알아보겠습니다.

로딩 됐을 때, 아무런 로딩 없는 사이트가 목표입니다.

prefetch with SWR


저는 캐싱 라이브러리로 SWR을 자주 사용하는데, SWR 은 prefetch 기능을 지원합니다.

두가지 방법이 있는데, index.html 에서 로드하는 방법과, 코드로 로드하는 방법이 있습니다.

index.html

<link rel="preload" href="/api/data" as="fetch" crossorigin="anonymous">

index.html 파일에 위 한줄만 추가하면, href 의 경로로 미리 데이터를 fetch 합니다.

App.jsx

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 의 서버사이드 렌더링과 비슷한 느낌으로 생각하면 좋을 것 같습니다.

이미지 preload


위와 같이 데이터를 prefetch 해왔을 때, 이미지 파일이 있을 수 있습니다.
이미지는 대부분 url 로 저장되어 있기 때문에, 브라우저에서 다운로드 받는 과정이 필요합니다. 따라서 swr로 prefetch 해왔다고 해도, 이미지가 바로 보이지 않을 확률이 높습니다

prefetch 해온 값은 이미지url 값이지 이미지가 아니기 때문입니다.

useImagePreloader

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>
}

prefetch & preload

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 을 미리 로드한 후, 로드되었다면 렌더링 하는 코드입니다.

profile
프론트엔드 개발자 입니다

0개의 댓글