FrontEnd

의식의 흐름에 따라 보는 <img> preload

SambaLim 2023. 9. 14. 19:51

의식의 흐름에 따라 보는 preload

전면팝업 Braze HTML의 이미지가 늦게 그려지는 현상이 발생하였습니다.

Nextjs에서 봤던 preload를 듣고 적용하여 팝업에 필요한 리소스 불러오는 시간을 네트워크 slow 3G 기준 8초 줄일 수 있었습니다.

Next.js

Next.js docs의 <Image> 컴포넌트 API로 가보았습니다.

priority 속성 설명에서 preload관련한 설명을 발견할 수 있었습니다.

priority={false} // {false} | {true}

When true, the image will be considered high priority and preload.

You should use the priority property on any image detected as the Largest Contentful Paint (LCP) element.

LCP(Largest Contentful Paint)

https://web.dev/i18n/ko/lcp/

페이지가 처음으로 로드를 시작한 시점을 기준으로 뷰포트 내에 있는 가장 큰 이미지 또는 텍스트 블록의 렌더링 시간을 보고합니다.

코드에서 찾아보기

packages/next/src/client/image-component.tsx

export const Image = forwardRef<HTMLImageElement | null, ImageProps>(...)

Ref로 HTMLImageElement, Props로 ImageProps 를 받아 컴포넌트를 만드는 것을 예측할 수 있다.

return (
  <>
    {
      <ImageElement
        {...imgAttributes}
        unoptimized={imgMeta.unoptimized}
        placeholder={imgMeta.placeholder}
        fill={imgMeta.fill}
        onLoadRef={onLoadRef}
        onLoadingCompleteRef={onLoadingCompleteRef}
        setBlurComplete={setBlurComplete}
        setShowAltText={setShowAltText}
        ref={forwardedRef}
      />
    }
    {imgMeta.priority ? (
      <ImagePreload
        isAppRouter={isAppRouter}
        imgAttributes={imgAttributes}
      />
    ) : null}
  </>
)

반환하는 부분을 보니 priority 에 따라 <ImagePreload> 를 반환하는 것을 볼 수 있습니다.

function ImagePreload({
  isAppRouter,
  imgAttributes,
}: {
  isAppRouter: boolean
  imgAttributes: ImgProps
}) {
  // ...

  return (
    <Head>
      <link
        key={
          '__nimg-' +
          imgAttributes.src +
          imgAttributes.srcSet +
          imgAttributes.sizes
        }
        rel="preload"
        // Note how we omit the `href` attribute, as it would only be relevant
        // for browsers that do not support `imagesrcset`, and in those cases
        // it would cause the incorrect image to be preloaded.
        //
        // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-imagesrcset
        href={imgAttributes.srcSet ? undefined : imgAttributes.src}
        {...opts}
      />
    </Head>
  )
}

<Head> 컴포넌트 내에 <link rel="preload" href="..." /> 형태로 사용하고 있는 것을 볼 수 있습니다.

적용

<head>
  <meta charset="UTF-8" />
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1.0, viewport-fit=cover"
  />
  <script>
  // NOTE: 이 부분을 수정하여 딥링크와 이미지를 변경할 수 있습니다.
  const sliderItemList = [
    {
    click_eventlog_name: 'banner1',
    href: 'link://main-shopping?tab=2325',
    img_url:
      "images/64ab5c66660a240e6a30b637/original.jpeg?1688951910",
    },
    {
    click_eventlog_name: 'banner2',
    href: 'link://main-shopping?tab=2161',
    img_url:
      "images/64b48e93ccaa4520ccec9f28/original.png?1689554579",
    },
    {
    click_eventlog_name: 'banner3',
    href: 'link://event-content?id=9704',
    img_url:
      "images/64ab5c65171a3d24b4a078be/original.png?1688951909",
    },
  ];

  document.head.innerHTML += sliderItemList.reduce(function(acc, item) {
    return acc + `<link rel="preload" as="image" href="${item.img_url}" />`;
  }, '');
  </script>

  // ...
</head>