Third Parties

요약: 서드파티 리소스로 인해 사이트 속도가 느려질 수 있고, 최적화가 어려울 수 있다. 특정 모범 사례을 참고하여 다양한 유형의 서드파티를 효율적으로 로드하거나 지연할 수 있다. 서드파티 스크립트 로드하기 위해 “언제” “어떻게” 구성하는지에 대한 템플릿 컴포넌트 형태로 제공하는 Next.js Script component와 같은 프레임워크 수준의 컴포넌트 요소를 사용할 수도 있다. 또한 Partytown과 같은 실험적인 아이디어를 살펴보는것도 좋다.

서드파티(이하 3P) 없이 단독으로 운영되는 현대의 웹 사이트는 찾기 어려울 것이다. 대부분의 사이트는 데이터, 함수, 컨텐츠 등과 같은 소스들과 함께 구성되어 있다. 우리가 구현하는 사이트에서 사용하는 리소스 중 다른 도메인에 있는 리소스들은 모두 3P 리소스이다. 사이트에는 아래와 같은 3P 리소스들이 사용된다.

  • 지도, 비디오, 소셜 미디어 및 채팅 서비스용 임베드
  • 광고
  • 애널리틱스 컴포넌트 및 태그 관리자 (구글)
  • A/B 테스트 및 개인화 스크립트
  • 데이터 시각화나 애니메이션와 같이 바로 사용 가능한 도움 기능을 제공하는 유틸리티 라이브러리
  • 봇탐지를 위한 reCAPTCHA 또는 CAPTCHA

3P의 기능을 코드베이스에 적용하여 컨텐츠에 새로운 가치를 부여하거나 사이트를 처음부터 구축하는 데 드는 수고를 줄일 수 있다. 2021년 웹 연감 보고서에 따르면 웹 페이지의 94% 이상이 3P를 사용하고 있다. 그 중에 이미지와 자바스크립트는 3P 컨텐츠가 가장 많은 역할을 하고 있다. 다음은 유형 및 범주별로 3P가 얼마나 사용되고 있는지에 대한 차트이다.

3P 리소스들은 웹 사이트의 다양한 기능들을 추가하는데 도움이 될 수 있지만, 다음의 경우 사이트의 속도를 늦출 수 있다.

  • 3P 도메인 서버에 대하여 추가적인 라운드 트립이 발생한다.
  • 무거운 자바스크립트를 실행하게 되거나 (다운로드 및 실행시간에 영향을 줌) 최적화 되지 않은 이미지나 비디오로 인해 리소스의 크기가 늘어난다.
  • 개발자 입장에서 3P의 구현에 영향력이 없고, 3P의 구현에 휘둘리게 된다.
  • 페이지 내에 다른 중요한 리소스의 렌더링을 차단하고 Core Web Vitals 에 악영향을 줄 수 있다.

이런 문제들이 있긴 하지만 3P는 웹 개발에 필수적인 경우가 많다. 3P를 제거할 수 없는 경우 그 다음으로 좋은 방법은 성능에 영향을 덜 주도록 3P를 최적화하는 것이다. 이 내용이 바로 이번 섹션에서 다룰 내용이다.

이어진 내용에서는 다양한 유형의 3P 스크립트 최적화를 위한 몇 가지 전략과 모범 사례를 포함하고 있다. 특히 Next.js의 Script 컴포넌트에 대한 모범 사례들이 많이 포함되어 있으며, 글의 끄트머리에서 다루고 있다. 먼저 3P 스크립트가 성능 저하를 일으키는지 여부를 확인하는 방법부터 살펴보자.

3P 리소스의 성능 영향도 평가

여러 기술들을 조합하여 3P가 우리가 개발한 웹 사이트에 어떤 영향을 주는지 확인할 수 있다.

문제가 있는 3P코드를 식별하는데 필요한 배경 지식들을 바탕으로 이를 최적화할 수 있는 전략들에 대해서 알아보자.

최적화 전략

3P코드를 직접 수정하여 최적화할 수 없으로 다음의 두 가지 선택지가 있다.

  1. 대체하거나 제거한다: 3P에서 제공하는 기능이 성능 감소에 비례하지 않는 경우 제거를 고려해야 한다. 가벼우면서도 동일한 기능을 제공하는 다른 3P를 찾을 수 있다. 동일한 기능을 제공하지만 더 가벼운 패키지로 변경하여 영화 앱을 개선한 사례를 참고하도록 하자.
  2. 로딩 순서를 최적화한다: 브라우저는 로딩 과정에서 여러 1P 스크립트와 3P 스크립트를 로드한다. 최적의 로딩 전략을 수립하려면 다양한 리소스들에 대해 브라우저에서 할당되는 우선 순위, 페이지에서의 위치, 각 리소스의 가치 등을 고려해야 한다. 필자가 제시했던 React/Next.js 앱에서의 최적화된 로딩 순서를 참고해 보자.

이제 이런 내용들이 다양한 3P에 적용되는 방식과 이를 최적으로 로드하기 위해 할 수 있는 조치들을 알아보자.

3P 스크립트를 효율적으로 로드해 보자

다음의 내용들은 올바르게 적용될 경우 3P 리소스로 인한 성능 저하를 줄일 수 있는 오랜 시간동안 검증된 모범 사례들이다.

자바스크립트의 다운로드 및 실행은 동기적이기 때문에 메인 스레드에서 HTML파서나 DOM의 생성을 방해할 수 있다. <script> 요소에서 async 혹은 defer를 사용하면 브라우저가 스크립트를 비 동기적으로 다운로드하도록 할 수 있다. 이를 이용하여 중요하지 않은 스크립트를 다운로드할 수 있다.

defer: HTML 구문 분석 중에 스크립트를 병렬로 다운로드하고 분석이 완료될 때 까지 실행이 지연된다. defer는 DOM 생성완료 전까지 스크립트의 실행을 지연시킬 수 있으므로 항상 사용하는것을 고려해야 한다.

async: HTML 구문 분석 중에 병렬로 다운로드하며 다운로드 즉시 메인 스레드를 블록하며 실행된다. 모듈 스크립트가 로드될 경우 해당 스크립트와 의존 모듈 모두 defer 큐에서 실행된다. 페이지의 로드 프로세스에 실행되어야 하는 스크립트에는 async를 사용해야 한다. 예를 들어 페이지의 초기 데이터를 읽을 수 있는 분석 스크립트를 일찍 실행할 수 있는 것이다.

<script src="https://example.com/deferthis.js" defer></script>
<script src="https://example.com/asyncthis.js" async></script>
출처: developers.google.com
출처: developers.google.com

주의할 점은 async 및 defer는 리소스의 로딩 우선순위를 낮추어 나중에 로드하게 된다는 점이다. 새로운 기능인 우선 순위 힌트는 이 문제를 해결하는 데 도움이 될 수 있다.

3P 리소스가 위치한 오리진에 연결하는 것은 각 서버마다 DNS조회, 리디렉션, 여러번의 라운드트립을 거쳐야 하기 때문에 매우 느려질 수 있다. dns-prefetch, preconnect와 같은 리소스 힌트들을 사용하면 이런 리소스들에 대한 연결을 더 일찍 하도록 하여 설정에 필요한 시간을 줄일 수 있게 된다.

도메인에 해당하는 dns-prefetch는 DNS조회가 일찍 수행되어 관련 대기 시간을 줄일 수 있다. 매우 중요한 리소스에 대해서는 preconnect를 사용할 수 있다. preconnect를 사용하면 TCP 라운드트립과 TLS 협상을 처리하고 추가로 DNS 조회도 진행하여 3P리소스와의 연결을 시작하게 된다.

<head>
  <link rel="preconnect" href="http://example.com" />
  <link rel="dns-prefetch" href="http://example.com" />
</head>

3P를 사용할 때의 이상적인 로딩 순서 글에는 preconnect를 사용해야 하는 3P 리소스의 목록이 나와 있다.

위의 리소스 힌트들을 사용할때의 이점은 Andy Davies의 preconnect를 사용하여 3P CDN이미지들에 대해 일찍 연결을 시작하는 것이 주요 제품 이미지들의 로드 시간을 줄이는 데 어떤 도움을 주는지에 대한 사례를 통해 알 수 있다.

  • 실제 측정 시 400ms 이상의 속도 개선이 이루어졌으며 95% 이상의 케이스들에서 1초 이상의 성능 향상을 관측할 수 있었다.

봇 감지 및 고객정보 동의 관리와 같은 중요한 3P의 로딩 시간을 최적화하는것 역시 가능하다.

소셜 미디어 피드, 광고, YouTube 동영상, 지도와 같이 임베드하여 사용하는 3P는 웹 페이지의 속도를 저하시킬 수 있다. 하지만 이런 임베드들은 페이지 로드와 즉시 사용자에게 보여지지 않을 수 있고 스크롤을 할 때 지연 로드하도록 할 수 있다. 아래는 이 때 사용 가능한 다양한 지연로딩 방법들이다.

  1. loading 속성은 이미지와 iframe들을 지연로딩하도록 할 수 있으며 YouTube 또는 Google Map과 같은 3P에도 사용할 수 있다.
  2. IntersectionObserver API 를 활용하여 특정 요소가 브라우저의 뷰포트에 들어오거나 나가는 것을 감지할 수 있다.
  3. Lazy-sizes 지연로딩을 구현하는 인기있는 자바스크립트 라이브러리이다.

다양한 지연 로딩 임베드는 페이지 로드 시 사용자에게 일시적으로 정적 혹은 동적인 파사드 화면을 보여준다. 실제 지도 임베드 대신 정적 이미지를 사용하여 지도를 표시할 수 있다. 또 임베드처럼 보이지만 사용자가 클릭하거나 상호작용을 할 때만 로드되는 파사드를 사용할 수 있다. 널리 알려진 임베드를 위한 파사드들은 Google Maps용 Map Static API, Twitter용 Tweetpik, YouTube용 lite-youtube-embed, 채팅 위젯용 React-live-chat-loader가 있다. 여기에서 이런 기술들에 대한 포괄적인 논의를 볼 수 있다.

지연 로딩 및 파사드 사용에 관련된 몇가지 주의 사항

  • YouTube 파사드의 동작은 macOS 11+의 iOS 및 Safari에서 약간 다르다. 처음 탭/클릭하면 실제 비디오 임베드가 로드된다. 사용자가 동영상을 재생하려면 다시 탭해야 한다.
  • 임베드의 크기가 지정되지 않은 경우 레이아웃 시프팅으로 이어지고 UX에 악영향을 줄 수 있다. 레이아웃 시프팅을 방지하려면 해당 컨테이너에 요소의 크기를 지정해야 한다.

preconnect, dns-prefetch를 사용하면 3P 리소스에 대한 연결을 일찍 시작할 수는 있지만 연결이 필요한건 같다. 또 3P 리소스 오리진이 최적화 되지 않은 경우라면 오롯이 캐싱 전략에만 의존해야 한다.

3P스크립트의 사본을 자체 호스팅할 경우 스크립트에 사용되는 로드 및 캐싱 프로세스를 더 잘 제어할 수 있다. 자체 호스팅은 DNS조회에 필요한 시간을 줄이고 HTTP캐싱을 사용하여 스크립트에 대한 캐싱 전략을 개선할 수 있도록 한다. HTTP/2 서버 푸시를 사용하여 사용자에게 필요한 스크립트를 직접 보낼수도 있다. 자체 호스팅 3P스크립트의 모범 사례는 Optimizely 에서 제공하는 자체 호스팅 3P스크립트를 통해 홈페이지의 렌더 시작 시간을 1.7초 개선한 casper.com 사례이다.

3P 스크립트 복사본을 자체 호스팅할 경우 원본의 변경 사항에 따라 정기적으로 업데이트해야 한다. 업데이트하지 않으면 스크립트가 오래되어 중요한 수정 사항이나 종속성관련 변경 사항들이 누락될 수 있다. CDN 대신 서버에서 자체 호스팅할 경우 CDN에서 사용하는 Edge Caching 메커니즘을 활용할 수 없게 된다.

자주 변경되는 스크립트는 자체 호스팅을 적용하기 애매할 수 있다. 서비스 워커를 활용하면 이러한 3P스크립트에 대한 캐싱을 개선하는 동시에 CDN Edge Caching도 활용할 수 있다. 이 기술을 사용하면 해당 리소스를 다시 다운로드하는 빈도를 더 잘 제어할 수 있다. 이 기술을 preconnect와 조합하여 다운로드 받는데 필요한 네트워크 비용을 추가로 줄일 수 있다. 또 페이지 내에 중요한 다른 처리 과정을 위해 중요하지 않은 3P스크립트들에 대한 요청을 지연할수도 있다.


다양한 3P 리소스들과 각 리소스들이 페이지에 제공하는 가치에 대해서 위의 가이드를 고려하길 바란다. 각 리소스의 용도에 따라 이상적인 로드 순서를 적용해 1P 리소스와 3P 리소스를 최적으로 조합하고 페이지의 속도를 높일 수 있다.

스크립트 유형 별 모범 사례

일부 스크립트들은 최적화하기 쉬운 경우가 있다. 다양한 3P 최적화, 관찰된 일반적인 제약 조건, 3P로드에 대한 희망 사항들에 대해서 웹 성능 전문가들과의 토론을 통해 흥미로운 결론을 도출했다. 의견이 일치했던 부분은 컨텐츠의 특정 모습이 표시되기 전 까지는 대부분의 사용자가 사이트와 상호작용하지 않는다는 것이었다. 다음은 다양한 스크립트의 유형 별 가이드이다.

중요하지 않은 스크립트

채팅 위젯이나 애널리틱스 스크립트와 같은 3P들은 사용자 경험에 중요하지 않으며 지연로딩을 적용할 수 있다. defer 속성을 사용하는 것은 이런 스크립트들의 로드 및 실행을 지연시키는 가장 일반적인 방법이다.

광고 또는 애널리틱스 팀은 이런 지연 로딩의 적용이 앱의 시인성과 광고 수익에 미치는 영향에 대해 걱정할 수 있다. Telegraph의 연구 사례가 이러한 맥락에 자주 인용되는데. 모든 스크립트들을 지연 로딩하더라도 애널리틱스 혹은 광고 지표가 왜곡되지 않는다고 한다. 오히려 첫 번째 광고 로드 항목이 평균 4초 이상 향상되었다고 한다. 일부 개발자들은 페이지가 상호작용이 가능해질때까지 3P로드를 지연시키는 솔루션도 만들어 쓰고 있다.

봇 감지 / ReCaptcha

봇이 웹 폼에 접근하는것을 방지하기 위해 개발자는 이러한 3P스크립트가 빨리 로드되도록 한다. 그러나 ReCaptcha에는 상당한 JS페이로드와 메인 스레드 블록이 발생하므로 필요할 때 까지 지연로딩을 적용해야 한다. 아래는 이런 스크립트를 최적화하는 방법들이다.

  1. 봇에 의해서 스팸을 받을 수 있는 폼이 존재하는 페이지에서만 로드한다.
  2. 사용자가 폼 요소에 상호작용을 할 때 스크립트를 지연 로드하도록 한다.
  3. 페이지 로드 시점에 스크립트를 로드해야 한다면 리소스 힌트를 활용한다.

Google 태그 관리자 (GTM)

큰 규모의 웹 사이트에서는 마케팅 팀이나 대행사에 대해 Google 태그 관리자 엑세스를 제공한다. 이를 사용해 모든 페이지들에 새 마케팅 태그를 추가할 수 있다. 성능은 마케팅 팀의 주요 관심사가 아니기 때문에 태그를 무분별하게 추가할 수 있고. 이는 성능 하락의 요인이 될 수 있다. GTM 스크립트 최적화는 GTM에 접근하는 사람을 제어하고 변경사항을 모니터링하는 것에 관한 것이다.

외부 대행사 대신 사이트 소유자가 GTM계정을 소유하고 있는지 점검하는 것부터 시작할 수 있다. 이를 통해 태그를 추가, 편집 및 게시할 수 있는 사람에 대한 세분화된 접근 권한을 정의할 수 있다. 새 태그를 감사하고 사용하지 않는 태그를 제거하도록 하여 개발 부서와 마케팅 부서 간의 더 나은 협업을 진행할 수 있다.

사이트의 모든 페이지에 GTM이 필요하지 않을 수 있다. (예를 들어 마케팅 팀이 결제 페이지의 이벤트를 추적할 이유는 없을 수 있다) 불필요한 GTM포함을 제거할 수 있도록 페이지들을 개별적으로 검토해야 한다. 쿠키 배너를 사용하는 사이트는 사용자가 쿠키를 거부하는 경우 GTM을 로드하지 않도록 할 수도 있다. 마지막으로 페이지에 GTM을 로드해야 하는 경우 기본 콘텐츠를 로드한 후 실행되도록 지연시킬수도 있다.

또 다른 최적화는 오래된 3P 스크립트 태그에 적용되는 document.write() 와 관련이 있다. document.write()를 사용하여 스크립트를 삽입하는 것은 안전하지 않고 브라우저나 스크립트 유형에 따라 경고 또는 오류가 발생할 수 있다. 일부 3P스크립트는 여전히 이 방법을 사용하고 있다. GTM은 document.wtite()를 호환하는 사용자 정의 HTML태그 생성 인터페이스를 설정에서 제공한다. 이 기능을 사용하면 GTM이 일시적으로 기본 document.write() 함수를 자체 보안 버전으로 대체한다.

A/B 테스트 및 개인화

사이트는 A/B테스트를 수행하여 웹 페이지의 어떤 버전이 더 나은 성능을 보이는지 확인한다. 식별된 유저들에게 두 가지 버전 중 하나을 로드하도록 한다. A/B테스트는 실행되는 페이지의 성능에 상당한 영향을 미칠 수 있으며 각 테스트는 1초 이상의 로딩 시간을 추가하게 된다. 현재 많은 A/B 테스트가 3P를 통해 외부에서 제공되고 있으며 개발자는 이러한 테스트의 UI를 변경하기 위해 실행되는 자바스크립트 코드를 제어할 수 없다.

사이트 개인화는 알려진 데이터를 기반으로 다양한 사용자에게 맞춤형 경험을 제공하기 위해 스크립트를 실행하는 것과 같은 컨셉이다. 이러한 스크립트는 무겁고 최적화 하기 어렵다. A/B 테스트와 마찬가지로 개안화 스크립트도 렌더링된 UI가 스크립트의 출력에 따라 달라지기 때문이 일찍 실행되어야 한다. A/B테스트 및 개인화를 위해 맞춤형 서버 기반 솔루션을 개발하는 것은 최적화를 위한 이상적인 방법이나. 모든 케이스에 적용할 수 있지는 않을 것이다.

3P A/B 테스트 스크립트를 최적화하기 위해 스크립트를 받는 사용자의 숫자를 제한할 수 있다. 스크립트는 휴리스틱을 통해 렌더할 버전을 결정하고 각 사용자에게 올바른 버전을 활성화하게 된다. 이로 인해 모든 사용자의 페이지 속도가 느려질 수 있다. Google의 최적화 도구를 사용하면 사용자를 타겟팅하기 위한 규칙을 구성할 수 있다. 이런 규칙의 대부분은 Google 서버에서 평가할 수 있기 때문에 대상이 아닌 사용자에게 성능에 미치는 영향이 적다.

YouTube 및 지도 임베드

이런 임베드는 무겁기 때문에 개발자는 임베드 로딩에 지연로딩을 적용하거나 상호작용 발생 시 로드하는 패턴을 적용해야 한다. iOS/macOS-Safari 에서 파사드를 사용하여 비디오를 재생하려면 두번의 탭 또는 클릭이 필요하다는 점에만 유의하면서 lite-youtube-embed 와 같은 솔루션을 사용하는 것이 좋다.

소셜 미디어 임베드

일부 소셜 미디어 임베드는 스크립트를 지연 로딩하는 옵션을 제공한다 (예: data-lazy in Facebook embed) 이와 같이 옵션을 찾아보는 것으로 성능을 개선할 수 있다. 또 다른 대안은 수동으로 만든 이미지 파사드를 사용하거나 Tweetpik과 같은 도구를 사용하는 것이다.

즉시 사용할 수 있는 최적화!

3P최적화를 위해 개발팀은 리소스 힌트, 지연로딩, HTTP 캐싱 및 서비스 워커의 미묘한 차이들을 이해한 다음 이를 솔루션에 구현해야 한다. 일부 프레임워크와 라이브러리는 개발자가 이를 쉽게 할 수 있도록 캡슐화하여 제공하고 있다.

Builder.io 에서 만는 partytown은 메인 스레드 대신 웹 워커에서 리소스 사용량이 많은 스크립트를 실행하는 데 도움을 주는 실험적인 라이브러리이다. 이 라이브러리는 메인 스레드는 1P전용이여야 하고 중요한 렌더링 경로 진행중에 필요하지 않은 스크립트는 샌드박싱되어 웹 워커내부에 격리될 수 있다는 철학을 가지고 있다. partytown을 사용하면 cookies, localStorage, userAgent등과 같은 기본 스레드 API대한 엑세스도 함께 구성할 수 있다. API호출은 스크립트가 수행하는 작업에 대한 통찰을 얻기 위해 인자를 포함하여 로깅도 가능하다.

자바스크립트의 서비스 워커는 웹 워커와 메인 스레드간의 통신을 처리한다. partytown 스크립트는 HTML문서와 동일한 서버에서 자체적으로 호스팅되어야 한다. React 또는 Next.js앱과 함께 사용하거나 프레임워크 없이도 사용 가능하다. 웹 서버에서 실행할 수 있는 각 3P 스크립트는 다음과 같이 스크립트 태그 속성을 text/partytown으로 지정해야 한다.

<script type="text/partytown">
  // Third-party analytics scripts
</script>

해당 라이브러리는 React또는 Next.js 프로젝트에서 직접 사용할 수 있는 React Partytown Component를 제공한다. Next.js document의 경우 아래와 같이 document의 에 포함될 수 있다.

import { Partytown } from '@builder.io/partytown/react';
import Document, { Html, Head, Main, NextScript } from 'next/document';

export default class MyDocument extends Document {
 render() {
   return (
     <Html>
       <Head>
         <Partytown />
       </Head>
       <body>
         <Main />
         <NextScript />
       </body>
     </Html>
   );
 }

Partytown은 Google 태그 관리자와 같은 일반적인 애널리스틱 라이브러리를 위한 React구성 요소도 포함되어 있다. 다음 예제는 이것을 React/Next.js 프로젝트에 추가하는 방법을 보여준다.

import { Partytown, GoogleTagManager, GoogleTagManagerNoScript } from '@builder.io/partytown/react';
import Document, { Html, Head, Main, NextScript } from 'next/document';

export default class MyDocument extends Document {
 render() {
   return (
     <Html>
       <Head>
         <GoogleTagManager containerId={'GTM-XXXXX'} />
         <Partytown />
       </Head>
       <body>
         <GoogleTagManagerNoScript containerId={'GTM-XXXXX'} />
         <Main />
         <NextScript />
       </body>
     </Html>
   );
 }

Next.js 에서도 Script Component를 통해 서드파티 스크립트에 대한 즉시 사용 가능한 최적화를 제공한다. 이를 통해 다양한 서드파티의 로딩 성능을 향상시키는 방법을 살펴보자.

Next.js Script Component

Next.js 11은 2021년 중반에 Google의 Aurora 팀이 도입한 Conformance 방법론을 기반으로 하는 components와 함께 출시되었다. Conformance는 최적의 로딩 및 Core Web Vitals를 지원하기 위해 잘 설계된 솔루션과 규칙을 제공하는 시스템이다. Conformance는 모범 사례 코드들을 규칙들로 정의하여 개발자가 쉽게 구현할 수 있도록 한다. Strong defaults와 Actionalble Rules 가 이 시스템의 기반이다. 이런 규칙들은 개발자들이 올바르게 작업하도록 돕고 안티패턴이 침투하는것을 방지하는 데 도움이 된다.

Next.js Script Component는 로드 성능을 향상시키는 커스터마이징 가능한 템플릿을 통해 conformance를 구현한다. Script Component는 <script> 태그를 캡슐화하고 strategy속성을 사용하여 3P 스크립트의 로드 우선 순위를 설정할 수 있다. strategy 속성은 3가지의 값을 가질 수 있다.

  1. beforeInteractive: 페이지 상호작용이 가능해지기 전에 실행되어야 하는 중요 스크립트에 적용 (예: 봇 감지)
  2. afterInteractive: 페이지 상호작용이 가능해진 후 브라우저가 실행할 수 있는 스크립트에 사용한다. (예: 태그 관리자) 이는 기본값이며 defer를 사용하여 스크립트를 로드하는것과 동일하다.
  3. lazyOnload: 브라우저가 유휴 상태일 때 느리게 로드될 수 있는 스크립트에 사용한다.

strategy 속성을 사용하면 Next.js가 자동으로 모범 사례를 도입하여 최상의 로드 순서를 보장하며 스크립트를 로드하는데 도움이 된다. 아래와 같이 strategy 속성과 함께 스크립트 태그를 사용할 수 있다. 기본 HTML 스크립트 태그와 달리 next/head component 또는 pages/document.js에 next/script 태그를 배치하면 안된다.

Before:

import Head from 'next/head'

export default function Home() {
  return (
    <>
      <Head>
        <script async src="https://example.com/samplescript.js" />
      </Head>
    </>
  )
}

After:

// pages/index.js
// default strategy afterinteractive will apply when strategy not specified.
import Script from 'next/script'

export default function Home() {
  return (
    <>
      <Script src="https://example.com/samplescript.js" />
    </>
  )
}

Script Component를 사용하면 위에서 설명한 많은 사례들을 쉽게 적용할 수 있다. 이를 사용해 애널리스틱, 소셜 미디어, 유틸리티 라이브러리 등을 위한 3P 스크립트를 로드할 수 있다. 다음 예시는 위의 strategy를 다양한 유형의 3P 스크립트에 적용하는 방법들이다.

Polyfill 일찍 로드하기

중요 컨텐츠에 사용되는 특정 폴리필을 일찍 로드하려는 상황에서는 다음 Next.js문서의 예시와 같이 beforeInteractive strategy를 사용하여 폴리필을 로드할 수 있다.

import Script from 'next/script'

export default function Home() {
  return (
    <>
      <Script
        src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserverEntry%2CIntersectionObserver"
        strategy="beforeInteractive"
      />
    </>
  )
}

소셜 미디어 임베드 지연로딩

소셜 미디어 임베드, 특히 페이지 로드와 동시에 보여지지 않는 임베드는 사용자가 스크롤하거나 비활성 기간 동안 지연되거나 지연 로드를 사용할 수 있다. 다음 코드와 같이 lazyonload strategy를 사용할 수 있다.

import Script from 'next/script'

export default function Home() {
  return (
    <>
      <Script
        src="https://connect.facebook.net/en_US/sdk.js"
        strategy="lazyOnload"
      />
    </>
  )
}

로드 시점에 조건부로 코드 실행하기

특정 3P로드 직후 실행해야 하는 코드가 있을 수 있다. 이는 Script Component의 onload 속성으로 설정할 수 있다. 예를 들어 다음 코드는 사용자 동의에 따라 실행할 코드를 포함하는 방법을 보여준다.

<Script
  src={url} // consent management
  strategy="beforeInteractive"
  onLoad={() => {
    // If loaded successfully, then you can load other scripts in sequence
  }}
/>

스크립트 내에서 인라인 스크립트 사용하기

3P 컴포넌트의 로드와 함께 실행되어야 하는 인라인 스크립트들은 아래와 같이 작성할 수 있다.

import Script from 'next/script'

<Script id="show-banner" strategy="lazyOnload">
 {`document.getElementById('banner').removeClass('hidden')`}
</Script>

// or

<Script
 id="show-banner"
 dangerouslySetInnerHTML={{
   __html: `document.getElementById('banner').removeClass('hidden')`
 }}
/>

여기의 인라인 스크립트는 지연 로드된 3P 배너 광고의 가시성을 변경하는 데 사용된다. 인라인 스크립트는 dangerouslySetInnerHTML 속성을 사용하여 포함될 수도 있다.

3P 스크립트에 속성 전달하기

3P 컴포넌트가 사용해야 하는 특정 속성을 Script Component를 통해 전달할수도 있다. 아래 예시는 두 가지 추가 속성이 전달되는 방법을 보여준다.

import Script from 'next/script'

export default function Home() {
  return (
    <>
      <Script
        src="https://www.google-analytics.com/analytics.js"
        id="analytics"
        nonce="XUENAJFW"
        data-test="analytics"
      />
    </>
  )
}

애널리틱스 스크립트 로드하기

Google 애널리틱스(GA) 및 Google 태그 관리자(GTM)를 사용하여 사이트에 분석 도구를 포함하는 다양한 방법이 있다. Script component를 사용하여 Next.js 사이트에서 gtag.js 또는 analytics.js 스크립트를 최적으로 로드할 수 있다. 이러한 스크립트를 실행하려는 위치에 따라 _app.js(모든 페이지에 적용 가능) 또는 특정 페이지에서 로드할 수 있다.

다음과 같이 _app.js 내부에 스크립트 구성 요소를 포함하여 사이트의 모든 페이지에 GTM을 활성화할 수 있다.

import Script from 'next/script'
// + other imports

function MyApp({ Component, pageProps }) {
  // Other app code

  return (
    <>
      {/* Google Tag Manager - Global base code */}
      <Script
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
           (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
           new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
           j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
           'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
           })(window,document,'script','dataLayer', '${GTM_ID}');
         `,
        }}
      />
      <Component {...pageProps} />
    </>
  )
}
export default MyApp

이렇게 하는 대신 특정 페이지에만 analytics.js 를 로드하려는 경우 아래와 같이 할 수 있다.

import Script from 'next/script'
//other imports

const Home = () => {
  return (
    <div class="container">
      <Script id="google-analytics" strategy="afterInteractive">
        {`
         (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
         (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
         m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
         })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

         ga('create', 'UA-XXXXX-Y', 'auto');
         ga('send', 'pageview');
       `}
      </Script>
    </div>
    //Other UI related HTML
  )
}
import Script from 'next/script'
//other imports

const Home = () => {
  return (
    <div class="container">
      <Script id="google-analytics" strategy="afterInteractive">
        {`
         (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
         (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
         m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
         })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

         ga('create', 'UA-XXXXX-Y', 'auto');
         ga('send', 'pageview');
       `}
      </Script>
    </div>
    //Other UI related HTML
  )
}

export default Home

위의 두 예에서 스크립트는 strategy = afterInteractive로 로드된다.

결론

서버 리소스와 외부 리소스를 조합하여 서비스를 제공할 때에는 이런 리소스들간의 상호 작용을 자주 모니터링 해야 한다. 리소스 로딩 순서를 올바르게 지정하고 모범 사례를 따르는 것부터 시작할 수 있다. 또는 설계에 이런 모범 사례들이 적용된 프레임워크 혹은 솔루션을 사용할 수 있다.

사이트가 성정함에 따라 성능 리포팅과 정기적인 감사를 하면 중복을 제거하고 성능에 영향을 미치는 스크립트를 최적화 하는데 도움이 된다. 마지막으로 일반적으로 알려진 성능 문제가 있는 3P가 최종적으로 코드를 최적화하거나, 문제를 해결할 수 있는 대안을 제시하기를 바랄 수도 있다.