Preload
Preload(<link rel="preload">
)는 늦게 발견될 수 있는 중요한 리소스들을 더 일찍 요청하도록 할 수 있는 브라우저 측 최적화 기능이다. 만약 앱이 렌더링되는데 있어 중요한 리소스들의 로딩 순서를 수동으로 지정할 수 있다면 Core Web Vitals에 긍정적인 영향을 줄 수 있을 것이다. 다만 preload가 만병 통치약은 아니며 이를 사용할 때의 트레이드오프를 알고 있는 것이 중요하다.
<link rel="preload" href="emoji-picker.js" as="script">
...
</head>
<body>
...
<script src="stickers.js" defer></script>
<script src="video-sharing.js" defer></script>
<script src="emoji-picker.js" defer></script>
TTI(Time To Interactive)나 FIP(First Input Delay)를 최적화 할 때. preload는 상호작용 코드들이 포함된 번들을 로드할 때 유용하다. 이 때 주의할 점은 이렇게 우선순위가 높아진 스크립트 때문에 이미지나 폰트 등의 리소스들이 밀려 FCP(First Contentful Paint), LCP(Largest Contentful Paint)가 늦어지지 않도록 해야 한다.
퍼스트 파티 코드들의 로딩을 최적화하기 위해 <script defer>
를 사용하며 <head>
태그 안에 둘 수도 있고. <body>
태그 안에 두어 해당 리소스가 빨리 발견되도록 할 수도 있다.
SPA에서의 Preload
Prefetching 이 곧 사용될 지 모르는 리소스들을 캐시하는 방법이라면 preload는 즉시 사용될 리소스를 미리 로드한다. 앱 내에 웹 폰트들은 초기 렌더링과 즉시 적용되어야 하거나. 어떤 이미지들을 사용자에게 즉시 보여져야 할 수 있다.
일전에 구현했던 EmojiPicker
컴포넌트가 페이지의 초기 렌더링 시점에 즉시 보여야 한다고 가정해 보자. 이 코드가 메인 번들에 포함되어 있지는 않겠지만 병렬로 로드되어야 한다. 이 때 prefetch처럼 코드에 특정 주석을 사용하여 Webpack에게 해당 모듈을 preload해야한다고 알려줄 수 있다.
const EmojiPicker = import(/* webpackPreload: true */ './EmojiPicker')
“Webpack 4.6.0 이상에서는 import문에
/* webpackPreLoad: true */
주석을 사용하여 해당 모듈을 preload할 수 있다. 4.6.0 미만 버전에서도 이 기능을 이용하려면. preload-webpack-plugin을 사용하면 된다.”
앱을 번들해 보면 아래와 같이 EmojiPicker
가 preload될 것이라는 것을 콘솔에서 확인할 수 있다.
Asset Size Chunks Chunk Names
emoji-picker.bundle.js 1.49 KiB emoji-picker [emitted] emoji-picker
vendors~emoji-picker.bundle.js 171 KiB vendors~emoji-picker [emitted] vendors~emoji-picker
main.bundle.js 1.34 MiB main [emitted] main
Entrypoint main = main.bundle.js
(preload: vendors~emoji-picker.bundle.js emoji-picker.bundle.js)
번들 결과를 확인해 보면 문서의 <head>
태그에 rel="preload"
속성이 있는 <link>
태그가 추가된 것을 확인할 수 있다.
<link rel="prefetch" href="emoji-picker.bundle.js" as="script" />
<link rel="prefetch" href="vendors~emoji-picker.bundle.js" as="script" />
이렇게 처리된 EmojiPicker
는 초기 번들과 병렬로 다운로드된다. prefetch는 브라우저가 네트워크 상황을 고려하여 리소스를 받아오기를 결정하지만, preload는 즉시 미리 로드된다.
초기 렌더링 후 EmojiPicker
를 따로 받는 시간을 기다려야 하는것과 달리, 거의 즉시 해당 리소스를 사용할 수 있게 되었다. 하지만 이런식으로 리소스의 우선순위를 설정하게 되면 사용자의 장치 성능이나 인터넷 연결 상태에 따라 초기 로딩을 꽤 지연시킬 수도 있다. 초기 렌더링 후 1초 정도 이내에 즉시 출력되어야 하는 리소스에만 제한적으로 사용하는 것이 좋다.
Preload 와 script async 속성 함께 사용하기
스크립트는 우선순위를 높여 다운로드하고 싶지만 파서 처리 과정이 메인스레드를 블록하는것을 피하고 싶다면 preload와 async속성을 함께 이용하는것도 방법이다. 하지만 다른 리소스들의 다운로드가 지연될 수 있으니 주의가 필요하다.
<link rel="preload" href="emoji-picker.js" as="script">
<script src="emoji-picker.js" async>
Chrome 95 이상에서의 Preload
Chrome 95버전 부터 preload 대기열이 일부 건너뛰어지는 버그가 수정되었다. 따라서 해당 기능을 더 안정적으로 사용할 수 있게 되었다. 다음은 Google Chrome개발팀인 Patrick Meenan이 이야기하는 preload를 사용할 때의 권장 사항들이다.
- HTTP 헤더에 preload를 포함하면 다른 모든 리소스보다 우선시되어 다운로드 된다.
- 일반적으로 preload 리소스들은 우선순위가 “중간”인 리소스들보다 높으며, 파서가 발견하는 순서대로 로드된다. 따라서 HTML의 시작 부분에 포함시킬때 주의가 필요하다.
- 폰트 preload는 head의 마지막이나 body의 시작지점에 코드를 두는 것이 적합하다.
- preload로 지정된 모듈보다 이를 import하여 사용하는 스크립트 태그들이 먼저 로드되어야 한다. 기본적으로 스크립트가 어떤 의존 모듈을 동적으로 로드하는 경우. 해당 의존 모듈을 preload할 때
<link rel=preload>
태그가 그 “부모”(preload 모듈을 동적 import하는 스크립트)보다 뒤에 위치시켜야 한다. 그렇지 않으면 메인 스크립트 로딩 전에 의존 모듈이 로딩되어 버린다. 메인 스크립트가 구문 분석되고 실행될 때 의존 모듈이 로딩되고 있는 것이 올바른 순서이다. - preload처리 된 이미지들은 따로 지정하지 않는 한 낮은 우선순위를 갖게 되며 비동기 스크립트 혹은 기타 우선순위가 낮거나 매우 낮음 표시괸 태그를 기준으로 정렬되어야 한다.
결론
preload를 사용할 땐 주의가 필요하며 항상 앱에 미치는 영향을 측정할 것을 권장한다. 이미지에 preload를 설정한 경우 브라우저가 이를 빨리 발견하도록 할 수 있다. 잘못 사용할 경우 preload는 오히려 FCP를 증가시키고 의도한대로 동작하지 않을 수 있다. 또한 이런 preload가 효과를 보려면 서버가 preload 요청에 대한 처리를 제대로 지원해야 한다.
<link rel="preload">
를 사용하여 스크립트를 프리로드하지만 실행은 하지 않도록 할 수도 있다.
아래는 preload를 사용하는 다양한 방법에 대한 web.dev의 문서들이다.