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

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

Leena Sohoni
Leena Sohoni

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

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

第三方指令碼可讓網頁開發人員利用現有解決方案導入常見功能,並縮短開發時間。但這些指令碼的開發人員通常不會考量到對使用網站的效能影響。對於使用這些指令碼的開發人員來說,這些指令碼也是黑箱。

在不同類別的第三方要求中,網站下載的第三方位元組中,有相當一部分是透過指令碼下載。根據預設,瀏覽器會根據指令碼在文件中的位置決定優先順序,這可能會延遲發現或執行對使用者體驗至關重要的指令碼。

您應盡早載入版面配置所需的第三方程式庫,以便轉譯網頁。初始轉譯作業不需要的第三方應延後執行,以免阻斷主執行緒上的其他處理作業。Lighthouse 提供兩項稽核,用來標記會造成轉譯阻斷或主執行緒阻斷的程式碼。

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

請務必考量網頁的資源載入順序,以免關鍵資源延遲,且非關鍵資源不會阻斷關鍵資源。

雖然有最佳做法可降低第三方程式碼的影響,但並非所有人都知道如何針對所使用的每個第三方程式碼實作這些做法。這可能會很複雜,因為:

  • 平均來說,網站會在行動版和電腦版上使用 21 到 23 個不同的第三方服務 (包括指令碼)。每個類型的使用方式和建議可能有所不同。
  • 實作許多第三方服務時,會因是否使用特定架構或 UI 程式庫而有所不同。
  • 我們經常推出新的第三方程式庫。
  • 由於與同一第三方相關的業務需求各不相同,開發人員難以將其使用方式標準化。

Aurora 專注於第三方指令碼

Aurora 與開源網頁架構和工具的合作,其中一部分是提供強大的預設值和自訂工具,協助開發人員改善使用者體驗的各個層面,例如效能、無障礙設計、安全性和行動裝置就緒性。2021 年,我們專注於協助架構堆疊改善使用者體驗和網站使用體驗核心指標

為了達成改善架構效能的目標,我們採取了許多重要步驟,其中之一就是研究 Next.js 中第三方指令碼的理想載入順序。Next.js 等架構可提供實用的預設值和功能,協助開發人員有效載入資源 (包括第三方資源)。我們研究了大量的 HTTP Archive 和 Lighthouse 資料,找出在不同架構中阻斷轉譯的第三方最多。

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

在沒有架構元件的情況下,為第三方指令碼排序

可用的指南可減少轉譯阻斷指令碼的影響,提供以下方法,以便有效載入及排序第三方指令碼:

  1. 請在 <script> 標記中使用 asyncdefer 屬性,告知瀏覽器載入非關鍵的第三方指令碼,且不阻擋文件剖析器。初始網頁載入或首次使用者互動時,系統可能會將不必要的指令碼視為非必要指令碼。

       <script src="https://example.com/script1.js" defer></script>
       <script src="https://example.com/script2.js" async></script>
    
  2. 使用預先連線和預先擷取 DNS 功能,及早連線至必要來源。這樣一來,關鍵指令碼就能提早開始下載。

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

Next.js 指令碼元件

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

指令碼元件會建構 HTML <script> 標記,並提供選項,可使用策略屬性設定第三方指令碼的載入優先順序。

// 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" />

strategy 屬性可接受三個值。

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

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

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

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

開發人員可以使用 Script 元件,在應用程式中任何位置放置第三方指令碼,以便延後載入第三方,並在文件層級放置關鍵指令碼。這表示指令碼元件可能與使用指令碼的元件一同放置。重新整理後,系統會根據所用策略,將指令碼插入最初轉譯文件的標頭或內文底部。

評估成效

我們使用 Next.js 商務應用程式入門網誌的範本,建立兩個用於評估加入第三方指令碼的影響的示範應用程式。我們一開始直接在這些應用程式的網頁中加入 Google 代碼管理工具和社群媒體嵌入功能的常用第三方服務,後來則改為透過「程式碼」元件加入。接著,我們在 WebPageTest 上比較這些網頁的效能。

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

我們在示範用的商務應用程式範本中加入了第三方指令碼,如下所示。

之前 使用後
使用非同步的 Google 代碼管理工具 指令碼元件,兩個指令碼的策略皆為 afterInteractive
不使用非同步或延遲的 Twitter 追蹤按鈕
示範 1 的腳本和腳本元件設定 (含 2 個腳本)。

下列比較圖表顯示 Next.js 商務啟動包兩個版本的視覺進度。如圖所示,啟用指令碼元件並採用正確的載入策略後,LCP 發生時間提早了將近 1 秒。

比較圖表顯示 LCP 的改善情形

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

我們已將第三方指令碼加入以下範例網誌應用程式。

之前 使用後
使用非同步的 Google 代碼管理工具 為四個指令碼設定 strategy = lazyonload 的指令碼元件
使用非同步的 Twitter 追蹤按鈕
不使用異步或延遲的 YouTube 訂閱按鈕
LinkedIn 追蹤按鈕 (不使用異步或延遲)
示範 2 的腳本和腳本元件設定,其中包含 4 個腳本。
這部影片展示了索引頁面載入進度,其中包含和不包含指令碼元件的情況。使用 Script 元件後,FCP 改善了 0.5 秒。

如影片所示,在沒有 Script 元件的情況下,網頁的「首次顯示內容所需時間 (FCP)」為 0.9 秒,在使用 Script 元件的情況下則為 0.4 秒。

指令碼元件的後續規劃

雖然 afterInteractivelazyOnload 的策略選項可有效控管轉譯阻斷指令碼,但我們也正在研究其他可提高指令碼元件效用的選項。

使用 Web Workers

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

在目前的 Next.js 指令碼元件實作中,建議您將策略設為 afterInteractivelazyOnload,在主執行緒中延遲這些指令碼。我們建議日後推出新的策略選項 'worker',讓 Next.js 使用 PartyTown 或自訂解決方案,在網路工作者上執行指令碼。歡迎開發人員針對這份 RFC 提出意見。

盡量降低 CLS

第三方內嵌內容 (例如廣告、影片或社交媒體動態饋給內嵌內容) 在延後載入時,可能會導致版面配置變動。這會影響使用者體驗,以及網頁的累計版面配置位移 (CLS) 指標。只要指定嵌入內容載入作業的容器大小,即可盡量減少 CLS。

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

包裝函式元件

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

  1. 讓他們更輕鬆地加入熱門指令碼標記。
  2. 確保架構在幕後使用最理想的策略。

結論

第三方指令碼通常是為了在使用網站中加入特定功能而建立。為降低非必要指令碼的影響,我們建議延後執行這些指令碼,Next.js 指令碼元件預設會執行這項操作。開發人員可以確保,除非明確套用 beforeInteractive 策略,否則所附加的指令碼不會延遲重要功能。如同 Next.js 指令碼元件,架構開發人員也可以考慮在其他架構中建構這些功能。我們正積極與 Nuxt.js 團隊合作,探索類似元件的發布方式。根據意見回饋,我們也希望進一步強化指令碼元件,以涵蓋更多使用情境。

特別銘謝

感謝 Kara EricksonJanicklas RalphKatie HempeniusPhilip WaltonJeremy WagnerAddy Osmani 對這篇文章提供意見。