跳到主要內容
返回部落格

2019年2月11日 星期一

Next.js 8

發布者

今天我們很榮幸地介紹已可正式用於生產環境的 Next.js 8,其特色包含

如同以往,我們已盡力確保所有這些優點都完全向後相容。對於大多數 Next.js 應用程式,您只需要執行

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

我們感謝我們的社群以及所有對我們的成功抱持信心的人。自從我們上一篇部落格文章發布以來,我們已經看到像 AT&TStarbucksTwitch 等公司使用 Next.js 重新發布其面向公眾的網站和應用程式。

Serverless 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 https://127.0.0.1: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 頁面會在發生錯誤時隨需載入。

這表示預設情況下需要載入、剖析和執行的程式碼更少。

DX 改善

Next.js 的主要目標之一是以最佳的開發人員體驗提供最佳的生產環境效能。此版本包含許多根據使用者回饋的細微改進。

改進的隨需載入條目

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

例如,當造訪 https://127.0.0.1:3000/my-page 時,pages/my-page.js 檔案會隨需編譯,然後呈現頁面。

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

The on-demand entries flow
隨需載入條目流程

當頁面已超過 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 將在編譯開始之前監聽連線,這表示如果您在編譯完成之前前往 https://127.0.0.1:3000/,請求將等待初始編譯完成後再處理請求,而不是必須重新整理頁面直到它可用為止。

特別感謝 Brian Beck 實作此功能。

更快的靜態匯出

Next.js 專注於預先呈現的概念,作為實現高效能的方法。預先呈現有兩種形式

  • 伺服器呈現,其中每個請求都會觸發呈現。因此,終端使用者不必等待任何 JS 下載即可開始使用資料
  • 靜態呈現,其中我們輸出靜態檔案,這些檔案可以直接提供服務,而無需在伺服器上執行任何程式碼

從 Next.js 8 開始,如果您的機器有多個 CPU,透過 next export 進行靜態呈現將具有速度改進。

根據在具有 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>My page 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>My page 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-src 'self'

API 身份驗證範例

一直以來最常被請求的範例之一是如何在 Next.js 中針對外部 API(任何 API,以任何程式語言撰寫)進行身份驗證。

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

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

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

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

特別感謝 Juan Olvera 貢獻了這個範例。

社群

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

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

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