Back返回部落格

Next.js 9.3

Next.js 9.3 帶來了靜態網站生成改進、原生 SCSS 支援、更小的打包體積、靜態 404 頁面等新功能!

我們非常興奮地向您介紹 Next.js 9.3,主要功能包括:

所有這些改進都是非破壞性的且完全向後兼容。您只需執行以下命令即可更新:

Terminal
npm i next@latest react@latest react-dom@latest

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

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

Next.js 是第一個混合框架,允許您根據每個頁面的需求選擇最適合的技術。

Next.js 9.0 引入了自動靜態優化的概念。當頁面沒有阻塞式資料獲取需求(如 getInitialProps)時,它將在構建時自動渲染為 HTML。

還有更多情況下,您可能希望在構建時將頁面渲染為靜態 HTML,即使有阻塞式資料獲取需求。例如,由(無頭)內容管理系統 (CMS) 驅動的行銷頁面或網站的部落格部分。

我們與 SSG 和 next export 的重度使用者(如 HashiCorp)合作,並在 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: {}, // 將作為 props 傳遞給頁面元件
  };
}

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

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

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

pages/blog.js
// 您可以使用任何資料獲取庫
import fetch from 'node-fetch';
 
// posts 將在構建時由 getStaticProps() 填充
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}
 
// 此函數在 Node.js 環境的構建時調用。
// 它不會在客戶端調用,因此您甚至可以執行
// 直接資料庫查詢。請參閱「技術細節」部分。
export async function getStaticProps() {
  // 調用外部 API 端點以獲取文章
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // 通過返回 { props: posts },Blog 元件
  // 將在構建時接收 `posts` 作為 prop
  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: { ... } } // 請參閱下面的「paths」部分
    ],
    fallback: truefalse // 請參閱下面的「fallback」部分
  };
}

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
  • 如果頁面名稱使用 catch-all 路由,例如 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 }) {
  // 渲染文章...
}
 
// 此函數在構建時調用
export async function getStaticPaths() {
  // 調用外部 API 端點以獲取文章
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // 根據文章獲取我們想要預渲染的路徑
  const paths = posts.map((post) => `/posts/${post.id}`);
 
  // 我們將在構建時僅預渲染這些路徑。
  // { fallback: false } 表示其他路由應返回 404。
  return { paths, fallback: false };
}
 
// 此函數也在構建時調用
export async function getStaticProps({ params }) {
  // params 包含文章 `id`。
  // 如果路由類似於 /posts/1,則 params.id 為 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // 通過 props 將文章資料傳遞給頁面
  return { props: { post } };
}
 
export default Post;

fallback: true

如果 fallbacktrue,則 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();
 
  // 如果頁面尚未生成,將顯示此內容
  // 直到 getStaticProps() 完成運行
  if (router.isFallback) {
    return <div>載入中...</div>;
  }
 
  // 渲染文章...
}
 
// 此函數在構建時調用
export async function getStaticPaths() {
  return {
    // 僅 `/posts/1` 和 `/posts/2` 在構建時生成
    paths: [{ params: { id: 1 } }, { params: { id: 2 } }],
    // 啟用靜態生成額外頁面
    // 例如:`/posts/3`
    fallback: true,
  };
}
 
// 此函數也在構建時調用
export async function getStaticProps({ params }) {
  // params 包含文章 `id`。
  // 如果路由類似於 /posts/1,則 params.id 為 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // 通過 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: {}, // 將作為 props 傳遞給頁面組件
  };
}

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

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

以下是一個使用 getServerSideProps 在請求時獲取數據並渲染的範例:

pages/index.js
function Page({ data }) {
  // 渲染數據...
}
 
// 每次請求時都會調用此函式
export async function getServerSideProps() {
  // 從外部 API 獲取數據
  const res = await fetch(`https://.../data`);
  const data = await res.json();
 
  // 透過 props 將數據傳遞給頁面
  return { props: { data } };
}
 
export default Page;

了解更多關於 getServerSideProps 的內容,請參考數據獲取文件

預覽模式 (Preview Mode)

正如前文所述,靜態生成 (Static Generation) 在頁面從無頭 CMS (headless CMS) 獲取數據時非常有用。但當你在無頭 CMS 上撰寫草稿並希望立即預覽時,靜態生成就不太理想了。由於輸出是靜態的,預覽變更會變得困難,因為你必須重新生成該靜態頁面。

Next.js 中引入的 getStaticProps 開啟了新的可能性,例如在某些條件下利用 Next.js 的按需渲染能力。

舉例來說,當預覽無頭 CMS 中的草稿時,你可能希望繞過靜態渲染,並按需渲染帶有草稿內容的頁面,而非已發布的內容。你希望 Next.js 僅在這種特定情況下繞過靜態生成。

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

預覽模式允許用戶繞過靜態生成的頁面,按需渲染(伺服器渲染 (SSR))例如來自 CMS 的草稿頁面。

然而,你不僅限於特定的 CMS 系統。預覽模式直接與 getStaticPropsgetServerSideProps 整合,因此可以與任何類型的數據獲取解決方案一起使用。

預覽模式在使用 next start 時已經可用,或透過部署Vercel Edge Network 無縫使用。

親自試試預覽模式: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,相比之下,44% 使用原生 CSS,6% 使用 Less。

此外,next-sassnext-css 存在相同的缺失限制。這意味著你可以在專案的每個文件中導入 Sass 文件,但導入的 Sass 文件將對整個應用程式全局有效。

考慮到這些統計數據和反饋,我們很高興地宣布 Next.js 現在內建支援導入 Sass 樣式表。

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

Terminal
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';
 
// 在新的 `pages/_app.js` 文件中必須有此默認導出。
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

Terminal
npm install sass

現在,假設在 components/ 文件夾中有一個可重用的 Button 組件:

首先,創建 components/Button.module.scss,內容如下:

/*
你無需擔心 .error {} 與其他 `.css` 或 `.module.css` 文件衝突!
*/
$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"
      // 注意 "error" 類是如何作為導入的 `styles` 物件的屬性訪問的。
      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 的發布引入了自動靜態優化 (Automatic Static Optimization) 的概念,當頁面沒有阻塞數據需求時,Next.js 會在構建時自動將頁面生成為靜態 HTML。然而,有一個頁面沒有自動渲染為靜態 HTML: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/_error.js 來渲染 404。

pages/404.js
export default () => <h1>這是 404 頁面</h1>;

32+ kB 更小的運行時(15 kB+ Gzip)

Next.js 支援與 React 本身相同的瀏覽器,無需任何配置。這包括 Internet Explorer 11 (IE11) 和所有主流瀏覽器(Edge、Firefox、Chrome、Safari、Opera 等)。

作為此兼容性的一部分,我們還會將你的應用程式編譯為兼容 IE11:這讓你可以安全地使用 ES6+ 語法特性、Async/Await、物件 Rest/Spread 屬性等 — 全部無需任何配置。

此編譯過程的一部分還包括透明地注入必要的功能 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 的幫助!