Back返回部落格

Next.js 8

Next.js 8 引入了無伺服器模式 (Serverless Mode)、更小的打包體積、效能改進等新功能。

我們很榮幸今天推出正式版的 Next.js 8,主要功能包括:

一如既往,我們努力確保所有這些改進都完全向下相容。對於大多數 Next.js 應用程式,您只需要執行:

終端機
npm i next@latest react@latest react-dom@latest

我們感謝我們的社群以及所有相信我們會成功的人。自從我們上一篇部落格文章以來,我們已經看到像 AT&TStarbucksTwitch 這樣的公司使用 Next.js 重新推出了他們的公開網站和應用程式。

無伺服器 Next.js

Next.js 的無伺服器目標會從頁面輸出無伺服器函數

無伺服器部署通過將您的應用程式拆分為更小的部分(也稱為 lambdas)來顯著提高可靠性和可擴展性。在 Next.js 的情況下,pages 目錄中的每個頁面都會成為一個無伺服器 lambda。

無伺服器有許多好處。參考連結在 Express 的背景下討論了其中一些,但這些原則普遍適用:無伺服器允許分散的故障點、無限的可擴展性,並且採用「按使用付費」模式非常經濟實惠。

要在 Next.js 中啟用無伺服器模式,請在 next.config.js 中添加 serverless 建置 target

next.config.js
module.exports = {
  target: 'serverless',
};

serverless 目標會為每個頁面輸出一個獨立的 lambda。這個檔案完全獨立,不需要任何依賴即可執行:

  • pages/index.js => .next/serverless/pages/index.js
  • pages/about.js => .next/serverless/pages/about.js

Next.js 無伺服器函數的簽名類似於 Node.js HTTP 伺服器回調:

type Function = (req: http.IncomingMessage, res: http.ServerResponse) => void;

Next.js 為無伺服器部署提供了低階 API,因為託管平台有不同的函數簽名。通常情況下,您會希望用相容層包裝 Next.js 無伺服器建置的輸出。

例如,如果平台支援 Node.js http.Server 類:

server.js
const http = require('http');
const page = require('./.next/serverless/about.js');
const server = new http.Server((req, res) => page.render(req, res));
server.listen(3000, () => console.log('Listening on http://localhost:3000'));

總結

  • 實現無伺服器部署的低階 API
  • pages 目錄中的每個頁面都成為一個無伺服器函數 (lambda)
  • 建立最小的無伺服器函數(50 KB 基礎 zip 大小)
  • 針對函數的快速冷啟動進行了優化
  • 無伺服器函數具有 0 依賴(它們包含在函數包中)
  • 使用 Node.js 的 http.IncomingMessagehttp.ServerResponse
  • 通過在 next.config.js 中設置 target: 'serverless' 來選擇啟用
  • server 目標仍然完全支援並維護
  • publicRuntimeConfigserverRuntimeConfigserverless 模式下不支援。請改用建置時配置。

大幅降低建置時記憶體使用量

我們為 webpack 做出了貢獻,以改進 Next.js(以及整個 webpack 生態系統!)的建置效能和資源利用率。

這項努力使得記憶體使用量最多提高了 16 倍,且效能沒有任何下降

記憶體釋放得更快,且在大量壓力下(許多頁面)程序不再崩潰。

我們將很快深入探討我們是如何實現這一優化的。請關注 Next.js 部落格

建置時環境配置

在審查 Next.js 應用程式時,我們經常觀察到一個重複出現的模式,即添加 babel-plugin-transform-definewebpack.DefinePlugin 來為應用程式提供配置值。

在 Next.js 8 中,我們在 next.config.js 中引入了一個名為 env 的新鍵,以向後相容的方式提供相同的功能:

next.config.js
module.exports = {
  env: {
    customKey: 'MyValue',
  },
};

這將允許您在程式碼中使用 process.env.customKey。例如:

pages/index.js
export default function IndexPage() {
  return <h1>The value of customKey is: {process.env.customKey}</h1>;
}

process.env.customKey 將在建置時被替換為 'MyValue'

預取效能改進

Next.js 路由器允許您預取頁面以實現更快的導航:

pages/index.js
import Link from 'next/link';
 
export default function IndexPage() {
  return (
    <>
      <Link href="/about" prefetch>
        <a>To About Page</a>
      </Link>
    </>
  );
}

它的工作原理是預取具有 prefetch 屬性的每個連結的 JavaScript 包。

在 Next.js 8 之前的版本中,這意味著將 <script> 標籤注入到文件 <body> 中。

然而,這在打開頁面時引入了一些開銷,最明顯的是瀏覽器的「加載」指示會顯示得比您預期的時間更長,即使頁面已經可以互動。

在 Next.js 8 中,prefetch 使用 <link rel="preload"> 而不是 <script> 標籤。它還只在 onload 之後開始預取,以允許瀏覽器管理資源。

此外,Next.js 現在檢測 2G 網路和 navigator.connection.saveData 模式,以在較慢的網路連接上禁用預取。

更小的初始 HTML 體積

由於 Next.js 預渲染 HTML,它將頁面包裝到一個帶有 <html><head><body> 和渲染頁面所需的 JavaScript 文件的預設結構中。

Next.js 7 中,我們將初始負載優化到 1.50KB,這比前一版本減少了 7.4%。

我們能夠進一步將初始負載大小減少到 1.16KB,再減少 23%:

7.08.0差異
文件大小(伺服器渲染)1.50KB1.16KB23% 更小

我們減少大小的主要方式是:

  • 移除了頁面初始化的內聯腳本
  • /_error 頁面不再包含在每次頁面加載中

按需加載 /_error

當生產環境中發生錯誤時,會渲染 /_error 頁面以顯示發生了錯誤。

自從 Next.js 的第一個版本發布以來,/_error 頁面的腳本標籤就是初始 HTML 的一部分,這意味著即使沒有運行時錯誤,它也會被加載。

從 Next.js 8 開始,/_error 頁面在發生錯誤時按需加載。

這意味著預設情況下需要加載、解析和執行的程式碼更少。

開發體驗改進

Next.js 的主要目標之一是提供最佳的生產效能和最佳的開發者體驗。此版本包括許多基於用戶反饋的細微改進。

改進的按需載入條目

開箱即用,Next.js 會自動編譯僅正在_積極_開發的頁面。Next.js 不會在每次運行 next dev 時編譯 pages 目錄中的所有頁面。相反,它會在您訪問它們時編譯頁面。

例如,當訪問 http://localhost:3000/my-page 時,pages/my-page.js 文件會按需編譯,然後渲染頁面。

這確保開發者在啟動開發伺服器時不必等待所有頁面編譯完成,這在較大的應用程式上可能需要相當長的時間。它保持記憶體使用量低,並且編譯器快速,因為編譯器不需要在打包時考慮所有頁面。

按需載入條目的流程

按需載入條目的流程

當一個頁面在 25 秒內未被訪問時,它將從編譯器的建置快取中釋放,以保持編譯器快速並減少記憶體使用量。

Next.js 跟踪頁面被訪問的方式是使用輪詢機制。每 5 秒,會發送一個「on-demand-entries-ping」以使 Next.js 開發伺服器知道某個頁面正在被訪問。

自從此功能首次發布以來,ping 是使用 window.fetch 呼叫完成的,這意味著每次觸發 ping 時,它都會在瀏覽器開發工具的 consolenetwork 選項卡中顯示。

最常被請求的功能之一是能夠從瀏覽器開發工具中隱藏這些請求,因為這些請求可能會增加不必要的噪音。

我們很高興地宣布,在 Next.js 8 中,基於 fetch 的 ping 已被基於 WebSockets 的方法取代,這意味著 ping 仍然會發生,但只有在檢查 WebSocket 連接時才會可見。

特別感謝 JJ Kasper 在轉換到 WebSockets 方面的合作。

開發模式下更快的埠監聽

當啟動 Next.js 開發伺服器時,它必須執行一些初始編譯才能提供請求,預設情況下,Next.js 會等待此編譯步驟完成後再啟動 HTTP 伺服器,這意味著如果您運行 next dev 然後轉到瀏覽器,有時可能會收到「此站點無法訪問」消息,因為 HTTP 伺服器尚未開始監聽連接。

在 Next.js 8 中,HTTP 將在編譯開始之前監聽連接,這意味著如果您在編譯完成之前轉到 http://localhost:3000/,請求將等待初始編譯完成後再提供請求,而不必刷新頁面直到它可用。

特別感謝 Brian Beck 實現了此功能。

更快的靜態匯出

Next.js 專注於預渲染作為實現高效能的手段。預渲染有兩種形式:

  • 伺服器渲染,其中每個請求觸發一次渲染。因此,最終用戶不必等待任何 JS 下載即可開始消費數據
  • 靜態渲染,我們輸出可以直接提供的靜態文件,無需在伺服器上執行任何代碼

從 Next.js 8 開始,通過 next export 進行的靜態渲染如果您的機器有多個 CPU,將會有速度改進。

基於 4 CPU 核心 MacBook 的測試,通過利用所有核心預渲染頁面,匯出速度從大約每秒 25 頁提高到每秒 75 頁。

Next.js 會自動檢測 CPU 核心數量並相應地分配頁面,無需任何代碼更改。

特別感謝 Benjamin Kniffler 實現了此功能。

Head 元素去重

構建應用程式時的一個常見需求是更新頁面的 <head> 元素。例如設置 <title><meta name="viewport"> 以實現響應式設計。

Next.js 提供了一個內建組件來引入對 <head> 的更改:

pages/index.js
import Head from 'next/head';
 
export default function IndexPage() {
  return (
    <>
      <Head>
        <title>我的頁面標題</title>
      </Head>
    </>
  );
}

<Head> 組件甚至可以在不同的組件中多次使用,例如您的佈局組件可以設置一些預設的 head 標籤。

但是,您可能希望用不同的值覆蓋預設的 head 標籤,在舊版本的 Next.js 中,這會導致標籤在輸出中重複,因為沒有辦法去重標籤。

因此,現在可以為 <Head> 組件中的每個元素提供一個 key 屬性,這將自動去重具有相同 key 值的標籤。

當在兩個標籤上設置 key="viewport" 時,只有最後一個會被渲染。

pages/index.js
import Head from 'next/head';
export default function IndexPage() {
  return (
    <>
      <Head>
        <title>我的頁面標題</title>
        <meta
          name="viewport"
          content="initial-scale=1.0, width=device-width"
          key="viewport"
        />
      </Head>
      <Head>
        <meta
          name="viewport"
          content="initial-scale=1.2, width=device-width"
          key="viewport"
        />
      </Head>
    </>
  );
}

安全性改進

新增 crossOrigin 配置選項

在 Next.js 6 中,我們引入了在 pages/_document.js 中為 <Head><NextScript> 添加 crossOrigin 屬性的選項,但這並未涵蓋設置 cross-origin 的所有用例。

Next.js 有一個客戶端路由器,它會動態注入 <script> 標籤,這些標籤在注入時缺少 cross-origin 屬性。

為了確保所有 <script> 標籤都設置了 cross-origin,我們在 next.config.js 中引入了一個新的配置選項

next.config.js
module.exports = {
  crossOrigin: 'anonymous',
};

引入此選項的另一個好處是,不再需要自定義 pages/_document.js 來設置應用程式中的 cross-origin

之前的行為仍然支援,但會在開發中發出警告,以幫助開發者遷移到新引入的選項。

移除內嵌式 Javascript

當使用 Next.js 7 及以下版本時,若要啟用 內容安全政策 (CSP),使用者必須在政策中包含 script-src 'unsafe-inline',因為 Next.js 會建立一個內嵌的 <script> 標籤來傳遞資料,例如將 getInitialProps 的結果傳遞到客戶端。

在 Next.js 8 中,我們已將此內嵌的 script 標籤改為 JSON 標籤,以安全地傳輸到客戶端。這意味著 Next.js 不再包含任何內嵌的 script。

經過謹慎考量後,現在可以使用 script-src 'self'

API 驗證範例

歷來最常被要求的範例之一 是如何在 Next.js 中針對外部 API 進行驗證,無論是任何 API 或任何程式語言。

隨著 Next.js 8 的推出,我們也引入了一個新建立的範例:with-cookie-auth

此範例展示了如何針對外部 Node.js API 進行驗證,但所應用的概念適用於任何無狀態 API。

該範例使用 cookie 在伺服器端和客戶端渲染之間共享 token。

這樣一來,如果應用程式在伺服器端渲染,它仍然可以代表使用者獲取經過驗證的資料。

特別感謝貢獻此範例的 Juan Olvera

社群

自首次發布以來,Next.js 已被從財富 500 強公司到個人部落格的各種場景使用。我們非常高興看到 Next.js 的採用持續增長。

  • 我們已有超過 600 位貢獻者 提交了至少 1 次 commit。
  • 在 GitHub 上,該專案已獲得超過 34,400 次星標
  • 自首次發布以來,已提交超過 2600 個 pull request

Next.js 社群擁有超過 4,570 名成員加入我們!