最佳化 Next.js 中的第三方指令碼載入

瞭解 Next.js Script 元件背後的願景,該元件提供內建解決方案,可最佳化第三方指令碼的載入作業。

Leena Sohoni
Leena Sohoni
Houssein Djirdeh
Houssein Djirdeh

在行動裝置和電腦上放送的網站中,約有 45% 的要求是第三方要求,其中 33% 是指令碼。第三方程式碼的大小、延遲時間和載入速度,可能會大幅影響網站效能。Next.js Script 元件內建最佳做法和預設值,可協助開發人員在應用程式中導入第三方指令碼,同時解決潛在的效能問題。

第三方指令碼及其對效能的影響

第三方指令碼可讓網頁程式開發人員運用現有解決方案,導入常見功能並縮短開發時間。但這些指令碼的建立者通常沒有任何誘因,去考量對使用網站的效能影響。對使用這些指令碼的開發人員來說,這些指令碼也是黑盒子。

在網站下載的第三方要求中,指令碼占了相當大的比例。根據預設,瀏覽器會根據指令碼在文件中的位置決定優先順序,這可能會延遲探索或執行對使用者體驗至關重要的指令碼。

版面配置所需的第三方程式庫應提早載入,以便轉譯網頁。如果初始算繪不需要第三方,則應延後載入,以免阻擋主執行緒上的其他處理作業。Lighthouse 有兩項稽核,可標記會阻礙轉譯或主執行緒的指令碼。

Lighthouse 稽核:排除會妨礙顯示的資源,以及盡量減少使用第三方程式碼

請務必考量網頁的資源載入順序,以免重要資源延遲載入,或非重要資源阻礙重要資源載入。

雖然有最佳做法可減少第三方的影響,但並非所有人都知道如何為使用的每個第三方導入這些做法。這可能很複雜,原因如下:

  • 平均而言,網站會在行動版和電腦版上使用 21 到 23 個不同的第三方 (包括指令碼)。兩者的用途和建議可能有所不同。
  • 導入許多第三方服務時,可能會因是否使用特定架構或 UI 程式庫而有所不同。
  • 新的第三方資料庫會經常推出。
  • 由於不同業務對同一第三方有不同需求,開發人員很難標準化其使用方式。

Aurora 著重於第三方指令碼

Aurora 與開放原始碼網頁架構和工具合作,提供強大的預設值和專用工具,協助開發人員改善使用者體驗,例如效能、無障礙功能、安全性和行動裝置整備度。2021 年,我們致力協助架構堆疊改善使用者體驗和 Core Web Vitals 指標。

為了提升架構效能,我們採取一項重要步驟,就是研究 Next.js 中第三方指令碼的理想載入順序。Next.js 等架構具有獨特優勢,可提供實用的預設值和功能,協助開發人員有效率地載入資源,包括第三方資源。我們研究了大量的 HTTP 封存和 Lighthouse 資料,找出哪些第三方在不同架構中阻擋轉譯的情況最嚴重。

為解決應用程式中主執行緒封鎖第三方指令碼的問題,我們建構了指令碼元件。這個元件封裝了排序功能,可讓開發人員更妥善地控管第三方指令碼載入作業。

不使用架構元件,直接安排第三方指令碼的執行順序

現有指南提供下列方法,可有效載入第三方指令碼並安排執行順序,降低轉譯阻斷指令碼的影響:

  1. 請使用 async 或 defer 屬性搭配 <script> 標記,指示瀏覽器載入非關鍵的第三方指令碼,而不封鎖文件剖析器。如果網頁初始載入或使用者首次互動時不需要某些指令碼,這些指令碼可能就會視為非必要。

       <script src="https://example.com/script1.js" defer></script>
       <script src="https://example.com/script2.js" async></script>
    
  2. 使用 preconnect 和 dns-prefetch,提早與必要來源建立連線。這樣一來,重要指令碼就能提早開始下載。

       <head>
           <link rel="preconnect" href="http://PreconnThis.com">
           <link rel="dns-prefetch" href="http://PrefetchThis.com">
       </head>
    
  3. 在主要網頁內容載入完畢後,或使用者向下捲動到包含第三方資源和嵌入內容的網頁部分時,延遲載入這些內容。

Next.js 指令碼元件

Next.js 指令碼元件會實作上述方法來排序指令碼,並提供範本讓開發人員定義載入策略。指定合適的策略後,系統就會以最佳方式載入,不會封鎖其他重要資源。

Script 元件以 HTML <script> 標記為基礎,並提供使用 strategy 屬性設定第三方指令碼載入優先順序的選項。

// Example for beforeInteractive:
<Script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=IntersectionObserverEntry%2CIntersectionObserver" strategy="beforeInteractive" />

// Example for afterInteractive (default):
<Script src="https://example.com/samplescript.js" />

// Example for lazyonload:
<Script src="https://connect.facebook.net/en_US/sdk.js" strategy="lazyOnload" />

策略屬性可採用三個值。

  1. beforeInteractive:這個選項可用於必須在網頁可供互動前執行的重要指令碼。Next.js 會確保這類指令碼插入伺服器上的初始 HTML,並在其他自行整合的 JavaScript 之前執行。同意聲明管理、機器人偵測指令碼,或轉譯重要內容所需的輔助程式庫,都適合採用這項策略。

  2. afterInteractive:這是預設套用的策略,等同於載入含有 defer 屬性的指令碼。這項屬性適用於瀏覽器可在網頁互動後執行的指令碼,例如數據分析指令碼。Next.js 會在用戶端插入這些指令碼,並在網頁凍結後執行。因此,除非另有指定,否則使用 Script 元件定義的所有第三方指令碼都會由 Next.js 延遲,進而提供強大的預設值。

  3. lazyOnload:這個選項可用於在瀏覽器閒置時,延遲載入低優先順序的指令碼。這類指令碼提供的功能 (例如即時通訊或社群媒體外掛程式) 不需要在網頁可互動後立即載入。

開發人員可以指定策略,告知 Next.js 應用程式如何使用指令碼。這樣一來,架構就能套用最佳化和最佳做法來載入指令碼,同時確保最佳載入順序。

開發人員可以使用 Script 元件,將第三方指令碼放在應用程式中的任何位置,以便延遲載入第三方指令碼,並在文件層級放置重要指令碼。這表示指令碼元件可能與使用指令碼的元件位於同一位置。在凍結後,指令碼會插入初始算繪文件的標頭或內文底部,具體取決於所使用的策略。

評估影響

我們使用 Next.js 電子商務應用程式和入門網誌的範本,建立了兩個示範應用程式,協助評估加入第三方指令碼的影響。一開始,這些應用程式的頁面會直接加入 Google 代碼管理工具和社群媒體嵌入項目常用的第三方,然後透過 Script 元件加入。接著,我們在 WebPageTest 上比較這些網頁的成效。

Next.js 電子商務應用程式中的第三方指令碼

第三方指令碼已新增至電子商務應用程式範本,用於示範,如下所示。

之前 晚於
Google 代碼管理工具 (非同步) 兩個指令碼的策略皆為 afterInteractive 的指令碼元件
不含 async 或 defer 的 Twitter 追蹤按鈕
示範 1 的指令碼和指令碼元件設定,包含 2 個指令碼。

以下比較圖顯示 Next.js Commerce Starter-Kit 兩個版本的視覺效果進展。如圖所示,啟用 Script 元件並採用正確的載入策略後,LCP 發生時間提早了將近 1 秒。

幻燈片比較,顯示 LCP 改善

Next.js 網誌中的第三方指令碼

第三方指令碼已新增至示範網誌應用程式,如下所示。

之前 晚於
Google 代碼管理工具 (非同步) 四個指令碼皆使用 strategy = lazyonload 的指令碼元件
Twitter「追蹤」按鈕 (非同步)
不含 async 或 defer 的 YouTube 訂閱按鈕
不含 async 或 defer 的 LinkedIn 追蹤按鈕
示範 2 的指令碼和指令碼元件設定,包含 4 個指令碼。
影片:顯示索引頁面的載入進度,分別有和沒有指令碼元件。使用 Script 元件後,FCP 縮短了 0.5 秒。

如影片所示,在沒有 Script 元件的網頁上,First Contentful Paint (FCP) 發生在 0.9 秒,而有 Script 元件的網頁則發生在 0.4 秒。

指令碼元件的後續規劃

雖然 afterInteractive 和 lazyOnload 的策略選項可大幅控制會阻礙轉譯的指令碼,但我們也正在探索其他選項,以提高指令碼元件的實用性。

使用 Web Worker

Web worker 可用於在背景執行緒上執行獨立指令碼,釋出主執行緒來處理使用者介面工作,並提升效能。Web Worker 最適合用於將 JavaScript 處理作業 (而非 UI 工作) 從主執行緒卸載。用於客戶服務或行銷的指令碼通常不會與 UI 互動,因此可能適合在背景執行緒上執行。您可以使用輕量級第三方程式庫 PartyTown,將這類指令碼隔離到 Web Worker 中。

根據 Next.js 指令碼元件目前的導入方式,建議您將策略設為 afterInteractive 或 lazyOnload,延遲主執行緒上的這些指令碼。我們提議日後推出新的策略選項 'worker',讓 Next.js 使用 PartyTown 或自訂解決方案,在 Web Worker 上執行指令碼。歡迎開發人員對這份 RFC 提供意見。

盡可能降低 CLS

延遲載入廣告、影片或社群媒體動態消息等第三方嵌入內容,可能會導致版面配置位移。這會影響使用者體驗和網頁的累計版面配置位移 (CLS) 指標。只要指定載入嵌入內容的容器大小,就能盡量降低 CLS。

Script 元件可用於載入可能導致版面配置位移的嵌入內容。我們正在考慮擴充這項功能,提供有助於降低 CLS 的設定選項。這項功能可在指令碼元件本身或隨附元件中提供。

包裝函式元件

加入熱門第三方指令碼 (例如 Google Analytics 或 Google 代碼管理工具 (GTM)) 的語法和載入策略通常是固定的。這些指令碼可以進一步封裝在個別包裝函式元件中,開發人員只能存取一組最基本的應用程式專屬屬性 (例如追蹤 ID)。包裝函式元件可協助開發人員:

  1. 方便他們加入熱門指令碼標記。
  2. 確保架構在幕後使用最合適的策略。

結論

第三方指令碼通常是為了在消費網站中加入特定功能而建立。為降低非關鍵指令碼的影響,建議您延遲載入這些指令碼,Next.js Script 元件預設會這麼做。開發人員可以放心,除非明確套用 beforeInteractive 策略,否則納入的指令碼不會延遲重要功能。與 Next.js 指令碼元件一樣,框架開發人員也可以考慮在其他框架中建構這些功能。我們正積極與 Nuxt.js 團隊合作,設法推出類似的元件。我們也希望根據意見回饋,進一步強化指令碼元件,以涵蓋更多用途。

致謝

感謝 Kara Erickson、Janicklas Ralph、Katie Hempenius、Philip Walton、Jeremy Wagner 和 Addy Osmani 對本文提供意見。