跳到內容
返回部落格

2020年3月9日 星期一

Next.js 9.3

作者:

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

所有這些優點都是非破壞性的,並且完全向後相容。您只需要執行以下命令即可更新

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

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

在建置網站或 Web 應用程式時,您通常需要在 2 種策略之間做出選擇:靜態生成 (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: {}, // 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 並為路徑傳回以下內容

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 }) {
  // 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;

Fallback: true

如果 fallbacktrue,則 getStaticProps 的行為會變更,Next.js 將在建置時將提供的路徑渲染為 HTML。當路徑在建置時未生成時,它將在使用者請求頁面時按需生成。

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

觸發頁面生成的使用者將收到 fallback 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 將對伺服器執行 fetch,而不是在瀏覽器中執行 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 的一項新內建功能來解決此需求:預覽模式。

預覽模式允許使用者繞過靜態生成的頁面,以按需渲染 (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-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 Modules 成為包含組件級 Sass 的理想方式。CSS Module 檔案可以匯入到您應用程式中的任何位置

若要開始在您的應用程式中使用 Sass CSS Modules,請確認您已安裝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 Modules 是一項選用功能,僅針對具有 .module.scss 副檔名的檔案啟用。仍然支援一般的<link> 樣式表全域 Sass 樣式

在生產環境中,所有 CSS Module 檔案都會自動串連成許多最小化和程式碼分割的 .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 頁面是針對不存在的路徑渲染的,按需渲染頁面可能會導致成本增加和伺服器負載。

我們著手以 2 種方式讓您更容易成功

  • 預設的 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 的執行階段大小 (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,感謝你們的協助!