使用 Netlify 和 Next.js 分解龐大的構建
已發表: 2022-03-10這篇文章得到了我們在 Netlify 的親愛的朋友的大力支持,他們是來自世界各地的一群令人難以置信的人才,並為 Web 開發人員提供了一個可以提高生產力的平台。 謝謝!
使用靜態生成的網站的最大痛苦之一是隨著應用程序的增長,構建速度會越來越慢。 這是任何堆棧在某些時候都面臨的不可避免的問題,並且根據您使用的產品類型,它可能會從不同的角度發生。
例如,如果您的應用在生成部署工件時有多個頁面(視圖、路由),則這些路由中的每一個都將成為一個文件。 然後,一旦達到數千人,您就會開始想知道何時可以部署而無需提前計劃。 這種情況在電子商務平台或博客上很常見,它們已經是網絡的很大一部分,但不是全部。 不過,路線並不是唯一可能的瓶頸。
一個資源密集型的應用程序也最終會到達這個轉折點。 許多靜態生成器進行資產優化以確保最佳的用戶體驗。 如果沒有構建優化(增量構建、緩存,我們將很快實現),這最終也將變得難以管理——考慮遍歷網站中的所有圖像:一遍又一遍地調整大小、刪除和/或創建新文件。 一旦所有這些都完成了:記住 Jamstack 從內容交付網絡的邊緣為我們的應用程序提供服務。 所以我們仍然需要將東西從編譯它們的服務器移動到網絡的邊緣。
除此之外,還有另一個事實:數據通常是動態的,這意味著當我們構建和部署應用程序時,可能需要幾秒鐘、幾分鐘甚至一個小時。 與此同時,世界一直在旋轉,如果我們從其他地方獲取數據,我們的應用程序肯定會過時。 不能接受! 再次構建以更新!
構建一次,需要時更新
一段時間以來,基本上每個 Jamstack 平台、框架或服務都將解決龐大的構建問題放在首位。 許多解決方案都圍繞增量構建。 實際上,這意味著構建將與它們與當前部署的差異一樣龐大。
不過,定義差異算法並非易事。 為了讓最終用戶真正受益於這種改進,必須考慮緩存失效策略。 長話短說:我們不想讓沒有改變的頁面或資產的緩存失效。
Next.js 提出了增量靜態再生 ( ISR )。 本質上,它是一種為每條路由聲明我們希望它重建的頻率的方法。 在底層,它簡化了服務器端的大量工作。 因為每條路由(無論是否動態)都會在特定的時間範圍內重建自己,並且它完全符合 Jamstack 公理,即每次構建時都使緩存失效。 將其視為max-age
標頭,但用於 Next.js 應用程序中的路由。
要啟動您的應用程序,ISR 只需一個配置屬性即可。 在您的路由組件上(在/pages
目錄內)轉到您的getStaticProps
方法並將revalidate
鍵添加到返回對象:
export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }
上面的代碼片段將確保我的頁面每小時重建一次並獲取更多的口袋妖怪來顯示。
我們仍然時不時地獲得批量構建(在發布新部署時)。 但這允許我們將內容與代碼分離,通過將內容移動到內容管理系統(CMS),我們可以在幾秒鐘內更新信息,無論我們的應用程序有多大。 告別用於更新拼寫錯誤的 webhook!
按需構建器
Netlify 最近推出了 On-Demand Builders,這是他們支持 Next.js 的 ISR 的方法,但也適用於包括 Eleventy 和 Nuxt 在內的框架。 在上一屆會議中,我們確定 ISR 是朝著縮短構建時間邁出的一大步,並解決了很大一部分用例。 儘管如此,還是有一些警告:
- 基於持續部署的完整構建。
增量階段僅在部署之後和數據發生。 不可能以增量方式交付代碼 - 增量構建是時間的產物。
緩存按時間失效。 因此,可能會發生不必要的構建,或者所需的更新可能需要更長的時間,具體取決於代碼中設置的重新驗證期。
Netlify 的新部署基礎架構允許開發人員創建邏輯來確定他們的應用程序的哪些部分將基於部署構建以及哪些部分將被推遲(以及它們將如何被推遲)。
- 危急
無需採取任何行動。 您部署的所有內容都將基於push構建。 - 延期
應用程序的特定部分不會在部署時構建,它將在第一個請求發生時延遲到按需構建,然後它將作為其類型的任何其他資源進行緩存。
創建按需構建器
首先,將 netlify/functions 包作為devDependency
到您的項目中:
yarn add -D @netlify/functions
完成後,它就像創建一個新的 Netlify 函數一樣。 如果您沒有為它們設置特定目錄,請前往netlify/functions/
並為您的構建器創建一個任意名稱的文件。
import type { Handler } from '@netlify/functions' import { builder } from '@netlify/functions' const myHandler: Handler = async (event, context) => { return { statusCode: 200, body: JSON.stringify({ message: 'Built on-demand! ' }), } } export const handler = builder(myHandler)
從上面的代碼片段可以看出,按需構建器與常規的 Netlify 函數分開,因為它將其處理程序包裝在builder()
方法中。 此方法將我們的功能連接到構建任務。 這就是您只需要在必要時才延遲構建應用程序所需的全部內容。 從一開始就進行小型增量構建!
Netlify 上的 Next.js
要在 Netlify 上構建 Next.js 應用程序,應該添加 2 個重要的插件,以獲得更好的體驗:Netlify Plugin Cache Next.js 和 Essential Next-on-Netlify。 前者更有效地緩存您的 NextJS,您需要自己添加它,而後者對 Next.js 架構的構建方式進行了一些細微調整,因此它更適合 Netlify,並且默認情況下可用於 Netlify 可以識別的每個新項目是使用 Next.js。
使用 Next.js 的按需構建器
構建性能、部署性能、緩存、開發人員體驗。 這些都是非常重要的主題,但數量很多——並且需要時間來正確設置。 然後我們開始討論關於專注於開發人員體驗而不是用戶體驗的舊討論。 這是事情進入積壓中的隱藏位置以被遺忘的時間。 並不真地。
Netlify 得到了您的支持。 只需幾個步驟,我們就可以在 Next.js 應用程序中利用 Jamstack 的全部功能。 現在是時候捲起袖子,把它們放在一起了。
定義預渲染路徑
如果您之前在 Next.js 中使用過靜態生成,您可能聽說過getStaticPaths
方法。 此方法適用於動態路由(將呈現各種頁面的頁面模板)。 無需過多關注此方法的複雜性,重要的是要注意返回類型是具有 2 個鍵的對象,就像在我們的概念驗證中,這將是 [Pokemon] 動態路由文件:
export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
-
paths
是一個array
,執行與該路徑匹配的所有路徑,這些路徑將被預渲染 fallback
有 3 個可能的值:blocking、true
或false
在我們的例子中,我們的getStaticPaths
正在確定:
- 不會預渲染任何路徑;
- 每當調用此路由時,我們都不會提供備用模板,我們將按需呈現頁面並讓用戶等待,從而阻止應用程序執行任何其他操作。
使用 On-Demand Builders 時,請確保您的後備策略符合您應用程序的目標,Next.js 官方文檔:後備文檔非常有用。
在 On-Demand Builders 之前,我們的getStaticPaths
略有不同:
export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }
我們正在收集我們打算擁有的所有 pokemon 頁面的列表,將所有pokemon
對象映射到一個帶有 pokemon 名稱的string
,然後轉發返回帶有它的{ params }
對像到getStaticProps
。 我們的fallback
設置為false
,因為如果路由不匹配,我們希望 Next.js 拋出404: Not Found
頁面。
您可以檢查部署到 Netlify 的兩個版本:
- 使用 On-Demand Builder:代碼,實時
- 完全靜態生成:代碼,實時
該代碼也在 Github 上開源,您可以輕鬆地自行部署以檢查構建時間。 有了這個隊列,我們進入下一個主題。
構建時間
如上所述,前面的演示實際上是一個概念驗證,如果我們無法衡量,沒有什麼是真正的好或壞。 為了我們的小研究,我去了 PokeAPI 並決定捕捉所有的口袋妖怪。
出於可重複性的目的,我限制了我們的請求(到1000
)。 這些實際上並不全部在 API 中,但它強制所有構建的頁面數量相同,無論內容是否在任何時間點得到更新。
export const fetchPkmList = async () => { const resp = await fetch(`${API}pokemon?limit=${LIMIT}`) const { count, results, }: { count: number results: { name: string url: string }[] } = await resp.json() return { count, pokemons: results, limit: LIMIT, } }
然後在單獨的分支中將兩個版本發射到 Netlify,這要歸功於預覽部署,它們可以在基本相同的環境中共存。 為了真正評估兩種方法之間的差異,ODB 方法非常極端,沒有為該動態路由預渲染任何頁面。 雖然不推薦用於現實世界的場景(您將希望預渲染您的交通繁忙的路線),但它清楚地標誌著我們可以通過這種方法實現的構建時性能改進的範圍。
戰略 | 頁數 | 資產數量 | 構建時間 | 總部署時間 |
---|---|---|---|---|
完全靜態生成 | 1002 | 1005 | 2分32秒 | 4分15秒 |
按需構建器 | 2 | 0 | 52 秒 | 52 秒 |
我們的小 PokeDex 應用程序中的頁面非常小,圖像資源非常精簡,但部署時間的收益非常顯著。 如果一個應用有中到大量的路由,絕對值得考慮ODB策略。
它使您的部署更快,因此更可靠。 性能下降僅發生在第一個請求上,從後續請求開始,渲染的頁面將被緩存在邊緣,使得性能與完全靜態生成的性能完全相同。
未來:分佈式持久渲染
就在同一天,按需構建器被宣布並提前訪問,Netlify 還發布了他們對分佈式持久渲染 (DPR) 的評論請求。
DPR 是 On-Demand Builders 的下一步。 它利用這種異步構建步驟,然後緩存資產直到它們實際更新,從而利用更快的構建。 不再為 10k 頁面的網站構建完整版本。 DPR 使開發人員能夠通過可靠的緩存和使用按需構建器來完全控制構建和部署系統。
想像一下這個場景:一個電子商務網站有 10k 個產品頁面,這意味著構建整個應用程序進行部署大約需要 2 個小時。 我們不需要爭論這是多麼痛苦。
使用 DPR,我們可以在每次部署時設置前 500 個頁面。 我們最繁忙的流量頁面始終為我們的用戶準備好。 但是,我們是一家商店,即每一秒都很重要。 所以對於其他 9500 個頁面,我們可以設置一個構建後掛鉤來觸發它們的構建器——異步部署我們剩餘的頁面並立即緩存。 沒有用戶受到傷害,我們的網站以最快的速度更新,緩存中不存在的所有其他內容都被存儲了。
結論
儘管本文中的許多討論點都是概念性的,並且有待定義實現,但我對 Jamstack 的未來感到興奮。 作為一個社區,我們正在取得的進步圍繞最終用戶體驗展開。
您對分佈式持久渲染有何看法? 您是否在應用程序中嘗試過 On-Demand Builders? 在評論中讓我知道更多信息或在 Twitter 上給我打電話。 我真的很好奇!
參考
- “使用 Next.js 進行增量靜態再生 (ISR) 的完整指南”,Lee Robinson
- “使用按需構建器在 Netlify 上更快地構建大型站點”,Asavari Tayal,Netlify 博客
- “分佈式持久渲染:一種用於更快構建的新 Jamstack 方法,”Netlify 博客的 Matt Biilmann
- “分佈式持久渲染 (DPR)”,Cassidy Williams,GitHub