跳至內容
返回部落格

2020 年 3 月 9 日,星期一

Next.js 9.3

發佈者

我們很高興今天推出 Next.js 9.3,它包含

所有這些優點都具備向下相容性,不會造成程式碼中斷。您只需執行以下指令即可更新:

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

新一代靜態網站生成 (SSG) 支援

在建置網站或網頁應用程式時,您通常必須在兩種策略之間做出選擇:靜態生成 (SSG) 或伺服器端渲染 (SSR)。

Next.js 是第一個混合框架,允許您針對每個頁面選擇最適合您使用案例的技術。

Next.js 9.0 引入了自動靜態優化的概念。當頁面沒有像 getInitialProps 這樣的阻斷性資料提取需求時,它將在建置時自動渲染為 HTML。

在更多情況下,您可能希望在建置時將頁面渲染為靜態 HTML,即使存在阻斷性資料提取需求。例如,由(無頭)內容管理系統 (CMS) 支援的行銷頁面或網站的部落格區塊。

我們與 HashiCorp 等 SSG 和 next export 的深度使用者合作,並在 Next.js 歷史上評論最多的 RFC 中與社群廣泛討論了正確的限制,以建立一種新的統一方式來進行資料提取和靜態生成。

今天,我們非常興奮地宣布兩種新的資料提取方法:getStaticPropsgetServerSideProps。我們還提供了一種為動態路由的靜態頁面提供參數的方法:getStaticPaths

這些新方法與 getInitialProps 模型相比有許多優點,因為它們明確區分了哪些將會是靜態生成 (SSG) 與伺服器端渲染 (SSR)。

  • getStaticProps(靜態生成):在建置時獲取資料。

  • getStaticPaths(靜態生成):指定基於資料預先渲染的動態路由

  • getServerSideProps(伺服器端渲染):在每次請求時獲取資料。

  • 這些改進是 API 的新增功能。所有新功能都完全向下相容,並且可以逐步採用。沒有任何棄用,getInitialProps 將繼續保持目前的功能。我們鼓勵在新頁面和專案中採用這些新方法。

getStaticProps

如果您從頁面匯出一個名為 getStaticPropsasync 函式,Next.js 將在建置時預先渲染此頁面。當您想要從 CMS 渲染特定的靜態頁面時,這特別有用。

getStaticProps 總是在 Node.js 環境中運行,程式碼會自動從瀏覽器套件中移除 (tree-shaken),確保傳送到瀏覽器的程式碼更少。這樣您就不必擔心資料獲取程式碼在 Node.js 和瀏覽器環境(它們之間存在一些不一致)中的執行。

這允許您使用任何非同步甚至同步的資料獲取技術,包括 fetch、REST、GraphQL,甚至直接存取資料庫。

pages/posts/[id].js
export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

context 參數是一個包含以下鍵值的物件:

  • paramsparams 包含使用動態路由的頁面的路由參數。例如,如果頁面名稱是 [id].js,則 params 看起來會像 { id: ... }。要了解更多資訊,請查看動態路由文件。您應該將其與 getStaticPaths 一起使用,我們稍後會解釋。

以下是一個使用 getStaticProps 從 CMS 獲取部落格文章列表的範例:

pages/blog.js
// You can use any data fetching library
import fetch from 'node-fetch';
 
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}
 
// This function gets called at build time in the Node.js environment.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // By returning { props: posts }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  };
}
 
export default Blog;

我應該何時使用 getStaticProps?

您應該在以下情況下使用 getStaticProps

  • 渲染頁面所需的資料在使用者請求之前,於建置時即可取得。
  • 資料來自無頭 CMS。
  • 資料可以公開快取(非使用者特定)。
  • 頁面必須預先渲染(為了 SEO)並且速度非常快 — getStaticProps 會產生 HTML 和 JSON 檔案,這兩者都可以由 CDN 快取以提高效能。

要深入了解 getStaticProps,請參閱資料獲取文件

getStaticPaths 動態路由並且使用 getStaticProps,它需要定義一個在建置時必須渲染成 HTML 的路徑列表。

如果您從使用動態路由的頁面匯出一個名為 getStaticPathsasync 函式,Next.js 將會靜態預渲染 getStaticPaths 指定的所有路徑。

pages/posts/[id].js
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // See the "paths" section below
    ],
    fallback: true or false // See the "fallback" section below
  };
}

paths 鍵(必填)

paths 鍵決定哪些路徑將被預渲染。例如,假設您有一個使用名為 pages/posts/[id].js 的動態路由的頁面。如果您從這個頁面匯出 getStaticPaths 並為 paths 返回以下內容

return {
  paths: [
    { params: { id: 1 } },
    { params: { id: 2 } }
  ],
  fallback: ...
}

那麼 Next.js 將在建置時使用 pages/posts/[id].js 中的頁面組件靜態生成 posts/1posts/2

請注意,每個 params 的值必須與頁面名稱中使用的參數匹配

  • 如果頁面名稱是 pages/posts/[postId]/[commentId],那麼 params 應該包含 postIdcommentId
  • 如果頁面名稱使用全域捕捉路由,例如 pages/[...slug],那麼 params 應該包含一個陣列 slug。例如,如果這個陣列是 ['foo', 'bar'],那麼 Next.js 將在 /foo/bar 靜態生成頁面。

fallback 鍵(必填)

getStaticPaths 返回的物件必須包含一個布林值的 fallback 鍵。

Fallback: false

如果 fallbackfalse,則任何未由 getStaticPaths 返回的路徑都將導致404 頁面。如果您知道在建置時所有路徑都已知,這將非常有用。

以下是一個範例,它會針對名為 pages/posts/[id].js 的每個頁面預渲染一篇部落格文章。部落格文章列表將從 CMS 擷取並由 getStaticPaths 返回。然後,對於每個頁面,它會使用 getStaticProps 從 CMS 擷取文章資料。

pages/posts/[id].js
import fetch from 'node-fetch';
 
function Post({ post }) {
  // Render post...
}
 
// This function gets called at build time
export async function getStaticPaths() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => `/posts/${post.id}`);
 
  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false };
}
 
// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Pass post data to the page via props
  return { props: { post } };
}
 
export default Post;

後備方案:true

如果 fallback 設定為 true,則 getStaticProps 的行為會改變,Next.js 會在建置時將提供的路徑渲染成 HTML。當某個路徑在建置時沒有產生時,它會在使用者請求該頁面時按需產生。

當您的應用程式有許多可以靜態生成的路由,但您不希望僅在建置時生成一部分頁面而導致建置時間增加時,此功能會很有用。

觸發頁面生成的使用者會收到一個後備 HTML,這通常是一個帶有載入狀態的頁面。這樣做的原因是靜態 HTML 可以從 CDN 提供,確保頁面即使尚未生成也能保持快速載入。

按需靜態生成其他頁面的範例

pages/posts/[id].js
import { useRouter } from 'next/router';
import fetch from 'node-fetch';
 
function Post({ post }) {
  const router = useRouter();
 
  // If the page is not yet generated, this will be displayed
  // initially until getStaticProps() finishes running
  if (router.isFallback) {
    return <div>Loading...</div>;
  }
 
  // Render post...
}
 
// This function gets called at build time
export async function getStaticPaths() {
  return {
    // Only `/posts/1` and `/posts/2` are generated at build time
    paths: [{ params: { id: 1 } }, { params: { id: 2 } }],
    // Enable statically generating additional pages
    // For example: `/posts/3`
    fallback: true,
  };
}
 
// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Pass post data to the page via props
  return { props: { post } };
}
 
export default Post;

要瞭解更多關於 getStaticPaths 的資訊,請參閱資料提取文件

getServerSideProps

如果您從頁面匯出一個名為 getServerSidePropsasync 函式,Next.js 將在每次請求時渲染此頁面 (SSR)。

getServerSideProps 總是在伺服器端運行,程式碼會自動從瀏覽器套件中移除 (tree-shaken),確保傳送到瀏覽器的程式碼更少。這樣您就不必擔心資料提取程式碼在伺服器和瀏覽器環境(兩者之間存在一些不一致)中的執行。這在許多情況下可以提高效能,因為伺服器通常與資料來源的連線速度更快。它還可以透過減少暴露資料提取邏輯來提高安全性。

這允許您使用任何非同步甚至同步的資料獲取技術,包括 fetch、REST、GraphQL,甚至直接存取資料庫。

當使用 next/link 在頁面之間導航時,Next.js 不會在瀏覽器中執行 getServerSideProps,而是會向伺服器發出提取請求,伺服器將返回呼叫 getServerSideProps 的結果。

pages/index.js
export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

context 參數是一個包含以下鍵值的物件:

  • params:如果此頁面使用動態路由,params 會包含路由參數。如果頁面名稱是 [id].js,則 params 看起來會像 { id: ... }。要瞭解更多資訊,請查看動態路由文件
  • reqHTTP 請求物件
  • resHTTP 回應物件
  • query:查詢字串。

以下是一個使用 getServerSideProps 在請求時擷取資料並進行渲染的範例

pages/index.js
function Page({ data }) {
  // Render data...
}
 
// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`);
  const data = await res.json();
 
  // Pass data to the page via props
  return { props: { data } };
}
 
export default Page;

要深入瞭解 getServerSideProps,請參閱資料擷取說明文件

預覽模式

如同本文前面所討論的,靜態生成在您的頁面從無頭 CMS 擷取資料時非常有用。然而,當您在無頭 CMS 上撰寫草稿並希望立即在頁面上預覽草稿時,它並不理想。由於輸出是靜態的,預覽變更會變得更加困難,因為您必須重新產生該靜態頁面。

Next.js 中 getStaticProps 的引入開闢了新的可能性,例如在特定條件下利用 Next.js 的隨需渲染功能。

例如,當從無頭 CMS 預覽草稿時,您會希望繞過靜態渲染,並隨需使用草稿內容而不是已發佈的內容來渲染頁面。您希望 Next.js 僅針對這種特定情況繞過靜態生成。

我們很高興宣布 Next.js 的一項新內建功能來滿足此需求:預覽模式。

預覽模式允許使用者繞過靜態生成的頁面,以從例如 CMS 隨需渲染 (SSR) 草稿頁面。

然而,您不僅限於某些 CMS 系統。預覽模式直接與 getStaticPropsgetServerSideProps 整合,因此它可以與任何類型的資料擷取解決方案一起使用。

使用 next start 或透過部署Vercel 邊緣網路時,預覽模式已可用。

https://next-preview.vercel.app/ 上自行試用預覽模式

參考說明文件以深入瞭解預覽模式

與 CMS 供應商協作

getStaticProps 讓您可以從任何資料來源擷取資料,包含 CMS 系統。

我們正積極與 CMS 生態系統中的許多主要參與者合作,以提供與 Next.js 整合的範例和指南。

目前正積極開發的範例包括:

如果您的公司活躍於 CMS 生態系,我們很樂意與您合作!歡迎隨時透過電子郵件Twitter與我們的團隊聯繫。

內建 Sass 支援全域樣式表

Next.js 9.2 引進了內建的全域 CSS 樣式表支援,以提供更佳的預設值和更優化的結果來取代 next-css 外掛。

在發佈之後,我們不斷收到整合 Sass 支援的請求,因為許多遷移到 Next.js 的企業都擁有基於 Sass 的現有設計系統。

在調查 Next.js 外掛使用情況後,我們發現目前約有 30% 的 Next.js 應用程式使用 next-sass。相比之下,使用原生 CSS 的佔 44%,使用 Less 的佔 6%。

此外,next-sass 也與 next-css 一樣缺少相同的限制。這表示您可以在專案的每個檔案中匯入 Sass 檔案,但這個匯入的 Sass 檔案會成為整個應用程式的全域樣式。

在考量這些統計數據和意見回饋後,我們很高興地宣布 Next.js 現在內建支援匯入 Sass 樣式表。

要在您的應用程式中開始使用全域 Sass 導入,請安裝 sass

終端機
npm install sass

然後,在 pages/_app.js 中導入 Sass 檔案。

例如,假設您的專案根目錄中有一個名為 styles.scss 的樣式表

$primary-color: #333;
 
body {
  padding: 20px 20px 60px;
  margin: 0;
  color: $primary-color;
}

如果還沒有 pages/_app.js 檔案,請建立一個。然後,導入 styles.scss 檔案

pages/_app.js
import '../styles.scss';
 
// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

由於樣式表本質上是全域的,因此它們必須在自訂 <App> 元件中導入。這是避免全域樣式的類別名稱和順序衝突的必要步驟。

在開發過程中,以這種方式表達樣式表,讓您的樣式可以在您編輯時自動在頁面上更新。

在正式環境中,所有 Sass 和 CSS 檔案將自動串接成一個縮小的 .css 檔案。此 CSS 檔案將透過 <link> 標籤載入,並自動注入到 Next.js 產生的預設 HTML 標記中。

這個新功能完全向下相容。如果您正在使用 @zeit/next-sass 或其他 CSS 相關的外掛程式,此功能將會被停用以避免衝突。

如果您目前正在使用 @zeit/next-sass,我們建議您從 next.config.jspackage.json 中移除該外掛程式,以便在升級時遷移到內建的 Sass 支援。

內建 Sass CSS 模組支援元件級樣式

Next.js 現在支援使用 [name].module.scss 檔案命名慣例的 Sass 檔案的 CSS 模組

與先前在 Next.js 5+ 中使用 next-sass 的支援不同,全域 Sass 和 CSS 模組現在可以**共存**—— next-sass 要求應用程式中的所有 .scss 檔案都被視為全域或區域,但不能同時兼具。

CSS 模組透過自動建立唯一的類別名稱來在區域範圍內使用 Sass。這讓您可以在不同的檔案中使用相同的 Sass 類別名稱,而不必擔心衝突。

這種特性使 CSS 模組成為包含元件級 Sass 的理想方式。CSS 模組檔案**可以在應用程式的任何地方導入**。

要在您的應用程式中開始使用 Sass CSS 模組,請確保您已安裝 sass

終端機
npm install sass

現在,考慮在 components/ 資料夾中有一個可重複使用的 Button 元件

首先,使用以下內容建立 components/Button.module.scss

/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
$color: white;
 
.error {
  color: $color;
  background-color: red;
}

然後,建立 components/Button.js,導入並使用上述 CSS 檔案

components/Button.js
import styles from './Button.module.scss';
 
export function Button() {
  return (
    <button
      type="button"
      // Note how the "error" class is accessed as a property on the imported
      // `styles` object.
      className={styles.error}
    >
      Destroy
    </button>
  );
}

Sass 檔案的 CSS 模組是一項選用功能,僅適用於副檔名為 .module.scss 的檔案。仍然支援一般的 <link> 樣式表全域 Sass 樣式

在生產環境中,所有 CSS 模組檔案都會自動合併成多個經過縮小和程式碼分割的 .css 檔案。這些 .css 檔案代表應用程式中的熱執行路徑,確保每個頁面載入的 CSS 量最少,以利應用程式渲染。

如同上述,這項新功能完全向下相容。如果您正在使用 @zeit/next-sass 或其他 CSS 相關外掛,則會停用此功能以避免衝突。

如果您目前正在使用 @zeit/next-sass,我們建議您從 next.config.jspackage.json 中移除該外掛,改用內建的 Sass 支援。

404 頁面的自動靜態最佳化

Next.js 9 版本引入了自動靜態最佳化的概念。當頁面沒有阻斷性資料需求時,Next.js 會在建置時自動將頁面產生為靜態 HTML。然而,有一個頁面不會自動轉譯為靜態 HTML:404 頁面。404 頁面未自動靜態化的主要原因是,提供 404 功能的 /_error 頁面處理的任務不僅僅是 404 錯誤,例如,它還處理其他錯誤。

由於 404 頁面會針對不存在的路由進行渲染,因此依需求渲染頁面可能會導致成本和伺服器負載增加。

我們透過兩種方式讓您更容易上手:

  • 預設的 Next.js 體驗會產生靜態 404 頁面。
  • 自訂 404 頁面時,它仍然會確保您最終得到一個靜態頁面。

此功能完全向下相容,因此如果您目前有自訂的 pages/_error.js,它將繼續用於 404 頁面,直到您新增 pages/404.js 為止。

預設情況下為靜態 404 頁面

當您的應用程式沒有自訂的 pages/_error.js 頁面時,Next.js 會自動靜態產生 404 頁面,並在需要提供 404 頁面時使用該頁面。這會自動發生,無需進行任何更改。

使用 pages/404.js 的自訂 404 頁面

要覆寫預設的 404 頁面,您現在可以建立一個 pages/404.js,它在建置時仍會自動進行靜態最佳化。如果您的應用程式有 pages/404.js,則會使用此頁面而不是 pages/_error.js 來呈現 404 錯誤。

pages/404.js
export default () => <h1>This is the 404 page</h1>;

減少 32+ kB 的執行時期大小(Gzip 壓縮後 15+ kB)

Next.js 支援與 React 本身相同的瀏覽器,無需任何設定。這包含 Internet Explorer 11 (IE11) 和所有熱門瀏覽器(Edge、Firefox、Chrome、Safari、Opera 等)。

作為此相容性的一部分,我們也將您的應用程式編譯成與 IE11 相容:這讓您可以安全地使用 ES6+ 語法特性、Async/Await、物件的其餘/展開屬性等等,所有這些都不需要任何設定。

此編譯過程的一部分還包括透明地注入必要的特性 polyfill(例如 Array.fromSymbol)。然而,這些 polyfill 僅對 不到 10% 的網路流量 是必要的,在大多數情況下是為了支援 IE11。

從 Next.js 9.3 開始,Next.js 會自動載入支援舊版瀏覽器所需的 polyfill,並且只在這些舊版瀏覽器中載入 polyfill。

實際上,這意味著對於 超過 90% 的使用者 來說,您的*首次載入*大小將減少 32 kB 或更多。

對於依賴更多瀏覽器特性的更大應用程式來說,這些大小的節省會更加顯著。

此優化過程全自動,您無需修改任何應用程式即可享用!

社群

我們很高興看到 Next.js 的採用率持續增長。

  • 我們已經有超過 927 位獨立貢獻者。
  • 在 GitHub 上,該專案已獲得超過 46,600 個星標。
  • 範例目錄 擁有超過 226 個範例。

Next.js 社群現在已有超過 15,250 位成員。現在您可以在 GitHub 討論區找到社群,這是一個讓社群討論和提問的新地方! 加入我們!

我們感謝我們的社群以及所有幫助塑造此版本發行的外部回饋和貢獻。

特別感謝 Jeff Escalante 對新的資料提取方法提供的寶貴意見。

非常感謝所有為此版本做出貢獻的人:@arcanis、@lgordey、@ijjk、@martpie、@jaywink、@fabianishere、@dijs、@TheRusskiy、@quinnturner、@timneutkens、@lfades、@vvo、@adithwip、@rafaelalmeidatk、@bmathews、@Spy-Seth、@EvgeniyKumachev、@chibicode、@piglovesyou、@HaNdTriX、@Timer、@janicklas-ralph、@devknoll、@prateekbh、@ethanryan、@MoOx、@rifaidev、@msweeneydev、@motiko 和 @balazsorban44 的幫助!