추론적인 프리렌더링으로 빠르게 페이지 로드하기


원문: Leena Sohoni, Addy Osmani, https://web.dev/speculative-prerendering/

추론적인 프리렌더링(Speculative prerendering)과 크롬 체험판(Chrome origin trial)에 참여하는 방법에 대해 알아보자.

preconnect, preload, prefetch, prerender와 같은 리소스 힌트는 브라우저가 사용자에게 곧 어떤 리소스가 필요한지 알아내는 데 도움을 준다.

preconnectpreload는 선언적인 힌트이다. 브라우저가 이에 따라 동작하므로 다음 작업에 꼭 필요한 리소스에만 사용해야 한다.

prefetchprerender는 추론적인 힌트이다. 브라우저에서 특정한 리소스가 필요할 가능성이 높기 때문에 이를 가져와야 한다고 권장할 때 사용한다.

이 글에서는 추론적인 프리페치(prefetch)과 프리렌더링을 중점적으로 다룬다. 어떻게 이를 사용하고, 현재 구현의 문제점, 수준 높은 추론을 구현한 유명한 외부 라이브러리에 대해 알아볼 것이다. 브라우저에 동일 출처(same-origin) 추론적인 프리렌더링을 제공하는 기능이 개발 중이며, 구현 방법과 설계에 대해 자세히 알아보고 크롬 체험판에 참여할 수 있다.

prefetchprerender: 현재 구현

사용자는 자신이 관심 있는 링크 목록(예를 들어, 검색어나 사용자의 선호도에 맞는 상품 또는 글 목록) 중 상단에 있는 링크를 클릭할 가능성이 높다. 다시 목록 페이지로 돌아가면 그다음 링크를 클릭할 것이다. prefetchprerender는 이러한 사용자 행동과 관련된 지식을 바탕으로 한다. 개발자는 특정 페이지(A) 이후 어떤 페이지(B)가 요청될 가능성이 있는지 추론한다.

prefetch 힌트

개발자가 브라우저에게 페이지 B 또는 페이지 B의 특정 리소스를 미리 가져올 수 있도록 알리기 위해 페이지 A에 prefetch 힌트를 넣는다면, 브라우저는 페이지 A 처리에 영향을 주지 않고 유휴 상태인 동안 해당 리소스를 가져올 수 있다.

원본 페이지(예시에서 페이지 A)에서 prefetch를 사용하는 방법은 다음과 같다.

<link rel="prefetch" href="/results/" as="document">

위 코드에서 as 속성은 선택 사항이지만 이미 캐시에 리소스가 있는지 확인할 수 있도록 올바른 헤더를 설정하는 데 도움을 줄 수 있다.

prefetch 지원 및 구현 옵션은 지난 몇 년 동안 성숙해졌다.

  • 브라우저 지원 범위: Chrome 8, Firefox 2, Edge 12, Safari 지원 안 함 (출처)

prerender 힌트

prerender 힌트를 넣으면 브라우저는 페이지 B를 미리 렌더링한다. 페이지를 미리 렌더링하면 사용자가 실제로 페이지 B 링크를 클릭할 때 즉각적인 탐색이 가능해진다.

원본 페이지에서 prerender를 사용하는 방법은 다음과 같다.

<link rel="prerender" href="/next-page/">

그러나 prerender는 아직 명확히 정의되거나 보편적으로 구현되지 않았다.

  • 브라우저 지원 범위: Chrome 13, Firefox 지원 안 함, Edge 79, Safari 지원 안 함 (출처)

프리페치 구현

프리페치 동작에 대해 주의해야 할 몇 가지 중요한 사항이 있다. (특히 크롬에서)

  • 다음 페이지 전체를 미리 가져오거나 동일한 출처의 하위 리소스(ex. 다음 페이지에 필요한 스타일 시트나 스크립트)를 미리 가져올 수 있다.
  • 미리 가져온 리소스는 캐시 가능한 경우 HTTP 캐시에 저장된다.
  • 크롬은 캐시된 항목을 5분 동안 보관한다.
  • 리소스를 요청하거나 리소스가 필요하면 캐시에서 가져온다. 그러나 부분적으로 리소스 로드가 완료되지 않은 경우, 크롬에서 선택하여 로드를 계속 진행한다.
  • 리소스를 미리 가져오면 추가적인 네트워크 통신이 일어나기 때문에 사용자가 데이터 절약 모드를 활성화한 경우 권장되지 않는다. 연결 유형 및 사용자 데이터 기본 설정에 대한 기타 세부 정보는 네트워크 정보 API를 통해 확인할 수 있다.

상태가 없는 프리페치를 사용한 프리렌더링 구현

크로미움에서 기존에 구현prerender는 많은 메모리를 사용하고 있었다. 그래서 리소스를 미리 가져오지만 자바스크립트를 실행하거나 페이지의 어떤 부분도 미리 렌더링하지 않는 상태가 없는 프리페치가 더 낫다는 판단하에 사라지게 되었다. 기존의 프리렌더링은 약 100MiB의 메모리를 소모했고 미디어와 같은 특정 요소가 미리 렌더링될 때 UI를 방해할 가능성도 있었다.

상태가 없는 프리페치를 사용한 현재 구현 방식은 페이지 로드 시간을 줄이면서 약 45MiB의 메모리라는 상당히 적은 메모리를 소모한다. 가져오기는 prerender 리소스 힌트가 있는 링크 요소에 의해 실행된다. 가져오기가 없는 프리페치(no-fetch prefetch)는 보이는 탭에서 격리되고 UI를 방해할 수 없는 새로운 전용 렌더러에서 실행된다. 상태가 없는 프리페치 프로세스는 필요한 리소스를 캐시하지만 렌더링하지 않는다. 또한 DNS 캐시와 쿠키 저장소가 업데이트되었을 때를 제외하곤 브라우저의 상태를 수정하지 않는다. 새로운 렌더러는 모든 하위 리소스가 로드된 이후 종료된다. 새로운 렌더러는 사용자가 요청하여 페이지를 렌더링하기 위해 생성된다.

프리렌더링을 상태가 없는 프리페치로 구현했지만 우리의 목표인 즉각적인 페이지 로드는 여전히 이룰 수 없다. 즉각적인 페이지 로드를 달성하는 방법을 찾기 전에 프리렌더링 구현과 관련이 있을 수 있는 프리페치의 추론 로직을 구현하는 몇 가지 기본 옵션에 대해 살펴보자.

서드 파티 라이브러리를 이용한 똑똑한 추론 방법

개발자는 사람들이 어떻게 자신의 사이트를 사용하는지에 대한 통찰력을 가지고 있고, 그 지식을 사용하여 어떤 것을 프리페치 또는 프리렌더링해야 하는지 결정한다. 이러한 사용 추세는 시간에 따라 발전하는 경향이 있으므로 개발자는 사이트에서 사용할 수 있는 최신 분석 데이터를 바탕으로 리소스 힌트를 업데이트해야 한다.

또한 휴리스틱을 사용하여 런타임에 미리 가져와야 하는 리소스를 결정하는 quicklink, guess.js와 같은 라이브러리도 있다. 이를 사용하면 개발자는 어떤 것을 미리 가져와야 하는지 추측할 필요가 없다. 라이브러리는 사용 가능한 데이터를 바탕으로 결정을 내린다.

Quicklink는 교차점 관찰자 API(Intersection Observer API)를 사용하여 뷰포트에 있는 링크를 확인하고 사용자의 연결 상태가 느리지 않은 경우 브라우저가 유휴 상태일 때 링크를 미리 가져온다. 네트워크 정보 API를 사용하여 연결 유형과 데이터 절약 모드가 활성화 여부를 확인한다. Quicklink는 멀티 페이지 앱단일 페이지 앱 모두에서 탐색 속도를 높이기 위해 사용할 수 있는 경량 라이브러리이다.

Guess.js

Guess.js는 구글 애널리틱스 또는 비슷한 분석 제공 업체에서 생성한 리포트를 바탕으로 예측 프리페치(predictive prefetching)을 구현한다. 이러한 분석 기반 예측은 사용자에게 필요할 가능성이 있는 리소스를 미리 가져오는 데 사용된다.

지금부터는 브라우저 내부에 비슷한 로직을 만들 수 있는 방법과 이를 지원하기 위해 필요한 추가 도구를 살펴보자.

개선된 프리렌더링 해결 방법 구현

현재 프리렌더링 구현은 여러 제약 조건을 처리하지 않는다. 다음은 논의된 몇 가지 제한 사항과 프리렌더링을 개선하기 위해 제안된 해결 방법이다.

  • 트리거: 현재 <link rel="prerender">는 브라우저에서 프리렌더링을 활성화할 수 있는 유일한 트리거이다. 미리 렌더링할 수 있는 페이지의 모든 링크에 대해 추가 리소스 힌트가 필요하다. prerender의 필수 사항이 변경될 수 있으므로 제안된 해결 방법은 사용자가 특정 기준에 맞는 링크에 대해 총괄 트리거를 지정할 수 있도록 해야 한다. 이는 뒤에서 자세히 설명할 추론 규칙 API(speculation rules API)를 사용해 구현할 수 있다.
  • 교차 출처 프리렌더링(Cross-origin prerender): 프리렌더링의 타깃 링크가 동일 출처인 경우 추가적인 검사가 필요하지 않다. 그러나 다른 출처를 가리키는 경우 브라우저에서 처리해야 하는 개인 정보 문제가 있을 수 있다. 예를 들어, 실제 탐색이 일어나기 전에 응답을 개인화하지 않아야 하므로 교차 출처 페이지를 프리렌더링할 때 사용자 자격 증명은 생략되어야 한다. 그러나 사용자가 해당 페이지를 탐색할 때 사용자 관련 정보가 제공될 수 있다. 이 두 단계 접근 방식을 지원하려면 타깃 페이지가 교차 출처 페이지에 의해 프리렌더링되도록 옵트인하는 방법이 있어야 한다.
  • 프리렌더링 브라우징 컨텍스트(Prerendering browsing context): 현재 다양한 최상위 브라우징 컨텍스트를 사용할 수 있다. 예를 들어, 창의 탭이나 페이지의 iframe은 다른 브라우징 컨텍스트를 사용한다. 프리렌더링된 콘텐츠에 대해 비슷한 최상위 프리렌더링 브라우징 컨텍스트를 만들어야 한다. 프리렌더링 브라우징 컨텍스트는 보이지 않는 탭과 비슷해야 하며 추가 제약이 적용되어야 한다. 미디어를 재생하거나 권한 프롬프트와 같이 UI를 방해할 수 있는 모든 API는 해당 브라우징 컨텍스트에서 비활성화되어야 한다. 또한 프리렌더링 브라우징 컨텍스트는 활성화될 수 있다. 활성화는 사용자가 미리 렌더링된 페이지로 이동할 때 일어나야 한다. 활성화되면 미리 렌더링된 페이지는 새로운 최상위 브라우징 컨텍스트로 전환된다.
  • 포탈(Portal): 포탈은 페이지 간 원활하고 즉각적인 탐색이 가능하도록 새롭게 제안된 HTML 요소이다. 다음과 같이 미리 렌더링된 콘텐츠를 표시할 수 있다.

    <portal id="myPortal" src="https://example.com/"></portal>

    해당 요소는 미리 렌더링된 브라우저 컨텍스트에서 미리 렌더링된 페이지의 미리 보기를 제공한다. 이는 페이지 미리 보기에 제한된 권한이 있다는 걸 의미한다. 개발자는 클릭 이벤트와 같은 코드로 컨텍스트를 활성화하여 애니메이션이 있는 포탈을 임베딩 창의 전체 페이지 보기로 확장할 수 있다.

prefetchprerender의 브라우저 내 추론 규칙

이전 섹션에서 다룬 프리렌더링의 중요한 부분 중 하나는 제안된 추론 규칙 API를 통해 사용할 수 있는 프리렌더링 트리거이다. 추론 규칙 API는 특정한 기준에 맞는 페이지를 추측하고 미리 가져오거나 렌더링할 수 있는 전반적인 권한을 브라우저에 나타내기 위해 개발자가 사용할 수 있다.

규칙은 브라우저가 웹사이트에서 사용자가 관심을 가질만한 초기 페이지들을 알아내는 데 도움이 된다. 그런 다음 브라우저는 장치, 네트워크 특성, 페이지 구조, 뷰포트, 커서의 위치, 페이지의 과거 활동 등을 바탕으로 추가적인 휴리스틱을 적용하여 미리 렌더링하거나 가져올 페이지를 결정한다. 따라서 QuickLink 또는 Guess.js가 구현한 것과 같은 추측 로직은 브라우저 자체에서 구현될 수 있다.

추론 규칙은 인라인 스크립트 태그 또는 외부 리소스에서 JSON 객체로 명시할 수 있다. 예를 들어, prerender에 대한 추론 규칙은 다음과 같이 정의할 수 있다.

<script type="speculationrules">
   {
     "prerender": [
       {
         "source": "list",
         "urls": ["/page/2"],
         "score": 0.5
       },
       {
         "source": "document",
         "if_href_matches": ["https://*.wikipedia.org/**"],
         "if_not_selector_matches": [".restricted-section *"],
         "score": 0.1
       }
     ]
   }
</script>

여기에는 prerender 리소스 힌트에 대해 두 가지 유형의 규칙이 정의되어 있다.

  • 목록 규칙은 주어진 urls 목록에 적용된다. score 값은 사용자가 다음에 이 URL 중 하나를 탐색할 가능성이 얼마나 되는지를 나타내는 데 쓰인다. 점수 값은 0.0에서 1.0 사이 값이며 기본값은 0.5이다.
  • 문서 규칙은 브라우저가 페이지에 있는 모든 링크 요소를 추측할 수 있다는 걸 의미하는 문서에 적용된다. if_href_matches 또는 if_not_href_matches, if_selector_matches 또는 if_not_selector_matches 필터를 포함하여 링크 요소의 하위 집합을 선택할 수 있다.

href_matches는 링크 URL을 비교하는 데 사용되며,selector_matchesCSS 선택자를 비교하는 데 사용된다.

동일 출처 프리렌더링 체험

이전에 다룬 기능 중 일부를 포함하는 프리렌더링의 초기 구현은 크롬 94에서 98까지 실행되는 크롬 체험판에서 사용할 수 있다. 다음은 체험판에 포함된 주요 기능이다.

  • 트리거: 동일 출처 URL을 미리 렌더링하는 트리거를 명시하는 데 추론 규칙 API의 특정 기능을 사용할 수 있다. 현재 "목록 규칙" 형식만 지원한다. 또한 동일 출처 페이지에 대해 페이지 당 하나의 프리렌더링만 허용한다.

    <script type="speculationrules">
    {
      "prerender": [
        {"source": "list", "urls": ["https://a.test/foo"]}
      ]
    }
    </script>
    • 여러 규칙이 명시된 경우, 크롬은 항상 첫 번째 규칙에 따라 프리렌더링한다. 점수 속성은 사용되지 않는다. 규칙을 추가할 수 있지만 규칙 집합의 제거는 무시된다.
  • 제한된 API: 위치 정보(Geolocation), 웹 시리얼(Web serial), 알림, 웹 미디(Web MIDI) 및 유휴 탐지와 같이 UI를 방해할 수 있는 API는 미리 렌더링된 페이지가 활성화될 때까지 지연된다.
  • 세션 접근: 미리 렌더링된 페이지는 탭 수준 세션이 만들어질 때 세션 객체를 복제한다. 활성화될 때 이 복제본을 삭제하고 탭에서 최신 세션 객체를 다시 가져온다.
  • 리소스: 미리 렌더링된 페이지는 활성화되었을 때만 로드되는 교차 출처 iframe을 제외하고 일반 페이지처럼 모든 리소스를 로드할 수 있다. 쿠키와 저장소 API 또한 일반 페이지처럼 동작한다.
  • 체험판 사용: 추론 규칙이 명시된 페이지와 미리 렌더링될 타깃 페이지 모두에 체험판 토큰이 포함되어야 한다.

체험판 사용하기

체험판을 사용하기 시작하면 페이지가 프리렌더링 되는지 확인할 수 있고 기존의 크롬 도구를 이용하여 성능 영향을 연구할 수 있다.

페이지가 미리 렌더링 되었는지

chrome://process-internals 페이지에서 미리 렌더링된 페이지가 있는지 확인할 수 있다.

process-internals

추론 규칙을 통해 프리렌더링을 시작하는 페이지와 이미 프리렌더링된 페이지는 모두 같은 webcontents 블록 아래 있지만 prerender 키워드로 구분할 수 있다.

프리렌더링된 페이지가 활성화되었는지

프리렌더링 후 다음 단계는 활성화이다. 탐색 시 활성화된 페이지가 프리렌더링된 페이지이고 새로운 페이지가 로드되지 않았다는 것을 확인하기 위해 탐색한 후 개발자 도구의 콘솔을 이용할 수 있다. 다음 스크립트를 실행하여 activationStart 값을 콘솔에서 확인해라.

let activationStart = performance.getEntriesByType('navigation')[0].activationStart;
console.log(activationStart);

activationStart 값이 0이 아니라면 미리 렌더링된 페이지가 활성화되었다는 것이다.

즉각적으로 이루어졌는지

activationStart 값은 FP(First Paint) 값과 FCP(First Contentful Paint) 값을 비교하여 프리렌더링 페이지에 대한 사용자 중심 성능 메트릭을 결정할 수 있는 타임스탬프 값이다.

// 활성화 탐색이 시작 되었을 때
let activationStart = performance.getEntriesByType('navigation')[0].activationStart;

// FP가 발생했을 때
let firstPaint = performance.getEntriesByName('first-paint')[0].startTime;

// FCP가 발생했을 때
let firstContentfulPaint = performance.getEntriesByName('first-contentful-paint')[0].startTime;

console.log('time to first paint: ' + (firstPaint - activationStart));
console.log('time to first-contentful-paint: ' + (firstContentfulPaint - activationStart));

체험판을 사용하기 전 firstPaintfirstContentfulPaint 값을 비교하면 성능 영향을 측정하는 데 도움이 될 수 있다. 또한 체험판에서 성능을 측정할 때 실제 사용자 모니터링 방법(real user monitoring (RUM))을 이용하는 것을 권장한다.

데모

프리렌더링 체험판에 대한 간단한 데모를 확인하려면 크롬에서 enable-prerender2 플래그를 활성화해라.

enable-prerender2

데모는 <link rel=prerender>와 추론 규칙 API를 사용한 세 가지 다른 페이지 유형을 미리 렌더링하는 옵션을 제공한다. 사용 가능한 각 옵션을 클릭하여 프리렌더링이 일어나는지 확인할 수 있다.

demo-for-prerender

또한 페이지 링크를 클릭해서 각 사례의 전환을 비교할 수 있다.

추론 규칙을 사용한 Timer.html 추론 규칙을 사용하지 않은 Timer.html
with-speculation-rules without-speculation-rules

피드백 환영

이 체험판의 목적은 외부 라이브러리 의존 없이 브라우저를 통해 거의 즉각적인 페이지 로드를 제공하는 것이다. 동시에 사용자 경험을 방해하지 않고, 보다 정교한 프리렌더링 여정을 시작하는데 좋은 기회를 제공한다. 프리렌더링 체험판에 등록하고 특정 사용 사례에 얼마나 잘 동작하는지 확인해라. 체험판에 대한 피드백이 있다면 GitHub 레포지토리에 이슈를 등록해라.

프리페치를 위한 추론 규칙 사용에 대한 체험판도 현재 진행 중이다. 기존 prefetch 힌트를 브라우저에서 지원하는 추측 프리페치로 교체하려면 이 체험판에 등록해라.