跳到內容
返回部落格

2023 年 5 月 4 日 星期四

Next.js 13.4

作者

Next.js 13.4 是一個基礎版本,標誌著 App Router 的穩定性

自從六個月前 Next.js 13 發布以來,我們一直專注於為 Next.js 的未來——App Router——建立基礎,使其能夠逐步採用,而不會產生不必要的重大變更。

今天,隨著 13.4 的發布,您現在可以開始在生產環境中採用 App Router。

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

Next.js App Router

我們在 2016 年發布了 Next.js,旨在提供一種簡易的方式來伺服器渲染 React 應用程式,我們的目標是創建一個更動態、個人化和全球化的網路。

在最初的公告文章中,我們分享了 Next.js 的一些設計原則

  • 零設定。使用檔案系統作為 API
  • 僅限 JavaScript。所有事物都是函式
  • 自動伺服器渲染和程式碼分割
  • 資料獲取由開發人員決定

Next.js 至今已六年。我們最初的設計原則一直保持不變——隨著 Next.js 被越來越多的開發人員和公司採用,我們一直在對框架進行基礎升級,以更好地實現這些原則。

我們一直在開發下一代 Next.js,而今天透過 13.4,下一代技術已趨於穩定,可以開始採用。這篇文章將分享更多關於我們針對 App Router 的設計決策和選擇。

零設定。使用檔案系統作為 API

基於檔案系統的路由一直是 Next.js 的核心功能。在我們最初的文章中,我們展示了從單個 React 元件建立路由的範例

pages/about.js
// Pages Router
 
import React from 'react';
export default () => <h1>About us</h1>;

無需進行額外設定。將檔案放入 pages/ 中,Next.js 路由器就會處理剩下的事情。我們仍然喜歡路由的這種簡潔性。但是,隨著框架的使用量增加,開發人員希望使用它來構建的介面類型也隨之增加。

開發人員要求改進對定義版面配置、將 UI 片段巢狀化為版面配置以及在定義載入和錯誤狀態方面具有更大彈性的支援。這並不容易改造到現有的 Next.js 路由器中。

框架的每個部分都必須圍繞路由器設計。頁面轉換、資料獲取、快取、變更和重新驗證資料、串流、樣式化內容等等。

為了使我們的路由器與串流相容,並解決這些對增強版面配置支援的要求,我們著手構建新版本的路由器。

這就是我們在最初發布 版面配置 RFC 後得出的結果。

app/layout.js
// New: App Router ✨
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
app/page.js
export default function Page() {
  return <h1>Hello, Next.js!</h1>;
}

比您在這裡看到的更重要的是您沒有看到的。這個新的路由器(可以透過 app/ 目錄逐步採用)具有完全不同的架構,建立在 React 伺服器元件Suspense 的基礎之上。

這個基礎使我們能夠移除最初開發用於擴展 React 原始元件的 Next.js 特定 API。例如,您不再需要使用自訂 _app 檔案來自訂全域共用版面配置

pages/_app.js
// Pages Router
 
// This "global layout" wraps all routes. There's no way to
// compose other layout components, and you cannot fetch global
// data from this file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

使用 Pages Router,版面配置無法組合,並且資料獲取無法與元件並置。使用新的 App Router,現在支援此功能。

app/layout.js
// New: App Router ✨
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
app/dashboard/layout.js
// Layouts can be nested and composed
export default function DashboardLayout({ children }) {
  return (
    <section>
      <h1>Dashboard</h1>
      {children}
    </section>
  );
}

使用 Pages Router,_document 用於自訂來自伺服器的初始酬載。

pages/_document.js
// Pages Router
 
// This file allows you to customize the <html> and <body> tags
// for the server request, but adds framework-specific features
// rather than writing HTML elements.
import { Html, Head, Main, NextScript } from 'next/document';
 
export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

使用 App Router,您不再需要從 Next.js 匯入 <Html><Head><Body>。相反,您只需使用 React。

app/layout.js
// New: App Router ✨
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

建立新檔案系統路由器的機會也是解決路由系統許多其他相關功能請求的適當時機。例如

  • 以前,您只能從外部 npm 套件(例如元件庫)在 _app.js 中匯入全域樣式表。這是一種不太理想的開發人員體驗。使用 App Router,您可以在任何元件中匯入(和並置)任何 CSS 檔案。
  • 以前,使用 Next.js 選擇加入伺服器端渲染(透過 getServerSideProps)意味著在整個頁面完成水合作用之前,與應用程式的互動會被阻止。使用 App Router,我們已重構架構以與 React Suspense 深度整合,這意味著我們可以選擇性地水合頁面的部分內容,而不會阻止 UI 中的其他元件進行互動。內容可以從伺服器即時串流傳輸,從而提高頁面的感知載入效能。

路由器是使 Next.js 運作的核心。但它不僅僅關乎路由器本身,還關乎它如何整合框架的其餘部分——例如 資料獲取

僅限 JavaScript。所有事物都是函式

Next.js 和 React 開發人員希望編寫 JavaScript 和 TypeScript 程式碼,並將應用程式元件組合在一起。從我們最初的文章

pages/index.js
import React from 'react';
import Head from 'next/head';
 
export default () => (
  <div>
    <Head>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
    </Head>
    <h1>Hi. I'm mobile-ready!</h1>
  </div>
);

在未來版本的 Next.js 中,我們新增了 DX 改善功能,可為您自動匯入 React。

此元件封裝了可以在應用程式中的任何位置重複使用和組合的邏輯。與檔案系統路由結合使用,這意味著可以輕鬆開始構建感覺像編寫 JavaScript 和 HTML 的 React 應用程式。

例如,如果您想獲取一些資料,則原始版本的 Next.js 看起來像這樣

pages/index.js
import React from 'react';
import 'isomorphic-fetch';
 
export default class extends React.Component {
  static async getInitialProps() {
    const res = await fetch('https://api.company.com/user/123');
    const data = await res.json();
    return { username: data.profile.username };
  }
}

在未來版本的 Next.js 中,我們新增了 DX 改善功能,該功能 polyfill 了 fetch,因此您無需匯入 isomorphic-fetchnode-fetch,並且可以在用戶端和伺服器上使用 Web fetch API

隨著採用率的提高和框架的成熟,我們探索了資料獲取的新模式。

getInitialProps 在伺服器用戶端上執行。此 API 擴展了 React 元件,允許您建立 Promise 並將結果轉發到元件的 props

雖然 getInitialProps 今天仍然有效,但我們隨後根據客戶回饋迭代推進了下一代資料獲取 API:getServerSidePropsgetStaticProps

pages/index.js
// Generate a static version of the route
export async function getStaticProps(context) {
  return { props: {} };
}
// Or dynamically server-render the route
export async function getServerSideProps(context) {
  return { props: {} };
}

這些 API 更清楚地表明您的程式碼是在用戶端還是伺服器上執行,並允許 Next.js 應用程式 自動靜態最佳化。此外,它還允許 靜態匯出,使 Next.js 能夠部署到不支援伺服器的位置(例如 AWS S3 儲存貯體)。

但是,這並非「僅限 JavaScript」,我們希望更接近我們最初的設計原則。

自 Next.js 創建以來,我們一直與 Meta 的 React 核心團隊密切合作,以在 React 原始元件之上構建框架功能。我們的合作夥伴關係,加上 React 核心團隊多年的研究和開發,為 Next.js 提供了一個透過最新版本的 React 架構(包括 伺服器元件)實現我們目標的機會。

使用 App Router,您可以使用熟悉的 asyncawait 語法 獲取資料。沒有新的 API 需要學習。預設情況下,所有元件都是 React 伺服器元件,因此資料獲取安全地在伺服器上進行。例如

app/page.js
export default async function Page() {
  const res = await fetch('https://api.example.com/...');
  // The return value is *not* serialized
  // You can use Date, Map, Set, etc.
  const data = res.json();
 
  return '...';
}

至關重要的是,「資料獲取由開發人員決定」的原則得以實現。您可以獲取資料並組合任何元件。不僅僅是第一方元件,而是伺服器元件生態系統中的任何元件,例如 Twitter 嵌入 react-tweet,它旨在與伺服器元件整合並完全在伺服器上運行。

app/page.js
import { Tweet } from 'react-tweet';
 
export default async function Page() {
  return <Tweet id="790942692909916160" />;
}

由於路由器與 React Suspense 整合,您可以在載入部分內容時更流暢地顯示回退內容,並根據需要逐步顯示內容。

app/page.js
import { Suspense } from 'react';
import { PostFeed, Weather } from './components';
 
export default function Page() {
  return (
    <section>
      <Suspense fallback={<p>Loading feed...</p>}>
        <PostFeed />
      </Suspense>
      <Suspense fallback={<p>Loading weather...</p>}>
        <Weather />
      </Suspense>
    </section>
  );
}

此外,路由器將頁面導航標記為 轉換,使路由轉換可中斷。

自動伺服器渲染和程式碼分割

當我們創建 Next.js 時,開發人員仍然普遍需要手動設定 webpack、babel 和其他工具,才能使 React 應用程式運作。新增伺服器渲染或程式碼分割等進一步最佳化通常未在自訂解決方案中實作。Next.js 以及其他 React 框架創建了一個抽象層來實作和強制執行這些最佳實務。

基於路由的程式碼分割意味著 pages/ 目錄中的每個檔案都將被程式碼分割到其自己的 JavaScript 捆綁包中,從而有助於減少檔案系統並提高初始頁面載入效能。

這對於伺服器渲染的應用程式以及使用 Next.js 的單頁應用程式都很有利,因為後者通常在應用程式啟動時載入一個大型 JavaScript 捆綁包。但是,為了實作元件級程式碼分割,開發人員需要使用 next/dynamic 來動態匯入元件。

app/page.tsx
import dynamic from 'next/dynamic';
 
const DynamicHeader = dynamic(() => import('../components/header'), {
  loading: () => <p>Loading...</p>,
});
 
export default function Home() {
  return <DynamicHeader />;
}

使用 App Router,伺服器元件不包含在瀏覽器的 JavaScript 捆綁包中。用戶端元件預設會自動進行程式碼分割(透過 webpack 或 Next.js 中的 Turbopack)。此外,由於整個路由器架構都啟用了串流和 Suspense,您可以逐步將 UI 的部分內容從伺服器傳送到用戶端。

例如,您可以使用條件邏輯對整個程式碼路徑進行程式碼分割。在此範例中,您無需為登出的使用者載入儀表板的用戶端 JavaScript。

app/layout.tsx
import { getUser } from './auth';
import { Dashboard, Landing } from './components';
 
export default async function Layout() {
  const isLoggedIn = await getUser();
  return isLoggedIn ? <Dashboard /> : <Landing />;
}

Turbopack (Beta)

Turbopack 是我們正在透過 Next.js 測試和穩定的新捆綁器,有助於加快您在 Next.js 應用程式上工作時的本機迭代速度(透過 next dev --turbo),並很快加快您的生產構建速度(next build --turbo)。

自 Next.js 13 中的 Alpha 版本發布以來,隨著我們努力修復錯誤並新增對遺失功能的支援,我們看到採用率穩步增長。我們一直在 Vercel.com 和許多營運大型 Next.js 網站的 Vercel 客戶上對 Turbopack 進行內部測試,以收集回饋並提高穩定性。我們感謝社群在測試和向我們的團隊報告錯誤方面的支援。

六個月後的現在,我們已準備好進入 Beta 階段。

Turbopack 尚未完全具備與 webpack 和 Next.js 相同的功能。我們正在 此問題 中追蹤對這些功能的支援。但是,現在應該支援大多數使用案例。我們此 Beta 版的目標是繼續解決因採用率提高而導致的剩餘錯誤,並為未來版本的穩定性做好準備。

我們對改進 Turbopack 的增量引擎和快取層的投資不僅將加快本機開發速度,而且還將很快加快生產構建速度。敬請關注未來的 Next.js 版本,您將能夠執行 next build --turbo 以進行即時構建。

立即在 Next.js 13.4 中使用 next dev --turbo 試用 Turbopack Beta 版。

伺服器動作 (Alpha)

React 生態系統見證了圍繞表單、管理表單狀態以及快取和重新驗證資料的許多創新和想法探索。隨著時間的推移,React 對其中一些模式變得更加主觀。例如,建議用於表單狀態的 「非受控元件」

目前的解決方案生態系統要么是可重複使用的用戶端解決方案,要么是內建於框架中的原始元件。到目前為止,還沒有辦法組合伺服器變更和資料原始元件。React 團隊 一直在開發 一種用於變更的第一方解決方案。

我們很高興宣布支援 Next.js 中實驗性的伺服器動作,使您能夠在伺服器上變更資料,直接呼叫函式,無需建立中間 API 層。

app/post/[id]/page.tsx (伺服器元件)
import kv from './kv';
 
export default function Page({ params }) {
  async function increment() {
    'use server';
    await kv.incr(`post:id:${params.id}`);
  }
 
  return (
    <form action={increment}>
      <button type="submit">Like</button>
    </form>
  );
}

透過伺服器動作,您可以擁有強大的伺服器優先資料變更、更少的用戶端 JavaScript 和逐步增強的表單。

app/post/new/page.tsx (伺服器元件)
import db from './db';
import { redirect } from 'next/navigation';
 
async function create(formData: FormData) {
  'use server';
  const post = await db.post.insert({
    title: formData.get('title'),
    content: formData.get('content'),
  });
  redirect(`/blog/${post.slug}`);
}
 
export default function Page() {
  return (
    <form action={create}>
      <input type="text" name="title" />
      <textarea name="content" />
      <button type="submit">Submit</button>
    </form>
  );
}

Next.js 中的伺服器動作旨在與其餘資料生命週期深度整合,包括 Next.js 快取、增量靜態重新產生 (ISR) 和用戶端路由器。

透過新的 API revalidatePathrevalidateTag 重新驗證資料意味著變更、重新渲染頁面或重新導向可以在一個網路往返中完成,即使上游提供者速度較慢,也能確保在用戶端顯示正確的資料。

app/dashboard/posts/page.tsx (伺服器元件)
import db from './db';
import { revalidateTag } from 'next/cache';
 
async function update(formData: FormData) {
  'use server';
  await db.post.update({
    title: formData.get('title'),
  });
  revalidateTag('posts');
}
 
export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['posts'] } });
  const data = await res.json();
  // ...
}

伺服器動作旨在實現可組合性。React 社群中的任何人都可以在伺服器上構建和發布伺服器動作,並將它們分發到生態系統中。就像伺服器元件一樣,我們對用戶端和伺服器的可組合原始元件的新時代感到興奮。

伺服器動作 今天在 Next.js 13.4 中以 Alpha 版提供。透過選擇使用伺服器動作,Next.js 將使用 React 的實驗性發布通道。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
};
 
module.exports = nextConfig;

其他改進

  • 草稿模式:從您的無頭 CMS 獲取和渲染草稿內容。草稿模式在 pagesapp 中均可運作。我們增強和簡化了現有的預覽模式 API,該 API 繼續適用於 pages。預覽模式適用於 app——您應該使用草稿模式。

常見問題

App Router 穩定性代表什麼?

今天將 App Router 標記為穩定版並不意味著我們的工作已完成。穩定性意味著 App Router 的核心已準備好投入生產,並且已透過我們自己的內部測試以及許多 Next.js 早期採用者進行了驗證。

未來我們仍希望進行額外的最佳化,包括伺服器動作達到完全穩定性。對我們來說,重要的是推動核心穩定性,以幫助社群清楚了解他們今天應該從哪裡開始學習和構建應用程式。

App Router 建立在 React canary 通道之上,該通道現在已準備好讓框架採用伺服器元件等功能。了解更多

這對 Next.js Beta 版文件意味著什麼?

從今天開始,我們建議使用 App Router 構建新的應用程式。Next.js Beta 版文件已用於解釋 App Router 並從頭重寫,現在已合併回 穩定的 Next.js 文件中。您現在可以輕鬆地在 App 或 Pages Router 之間切換。

我們建議閱讀 App Router 逐步採用指南,以了解如何採用 App Router。

Pages Router 會被淘汰嗎?

不會。我們致力於支援 pages/ 開發,包括錯誤修復、改進和安全修補程式,以適用於未來多個主要版本。我們希望確保開發人員有足夠的時間在他們準備好時逐步採用 App Router。

支援並鼓勵在生產環境中同時使用 pages/app/。App Router 可以 按路由方式採用。

這是否意味著伺服器元件「已完成」?

Next.js 是一個選擇基於 React 架構(包括伺服器元件)構建的框架。我們希望透過 App Router 提供的體驗能夠鼓勵其他框架(或新框架)也考慮使用此架構。

此生態系統中仍有一些模式尚未定義,例如處理無限滾動。目前,我們建議在生態系統發展和庫創建或更新時,針對這些模式使用用戶端解決方案。

社群

Next.js 是超過 2,600 位個別開發人員、Google 和 Meta 等行業合作夥伴以及我們 Vercel 核心團隊共同努力的成果。加入 GitHub DiscussionsRedditDiscord 社群。

本次發布由以下人員為您帶來

以及以下貢獻者:@shuding、@huozhi、@wyattfry、@styfle、@sreetamdas、@afonsojramos、@timneutkens、@alexkirsz、@chriswdmr、@jankaifer、@pn-code、@kdy1、@sokra、@kwonoj、@martin-wahlberg、@Kikobeats、@JTaylor0196、@sebmarkbage、@ijjk、@gnoff、@jridgewell、@sagarpreet-xflowpay、@balazsorban44、@cprussin、@ForsakenHarmony、@li-jia-nan、@dciug、@albertothedev、@DuCanhGH、@feedthejim、@patrick91、@padmaia、@sophiebits、@eps1lon、@reconbot、@acdlite、@cjmling、@nabsul、@motopods、@hanneslund、@tunamagur0、@devknoll、@apeltop、@maranomynet、@y-tsubuku、@EndangeredMassa、@ykzts、@AviAvinav、@adilansari、@wyattjoh、@charkour、@delbaoliveira、@agadzik、@Just-Moh-it、@rodrigofeijao、@leerob、@juliusmarminge、@koba04、@Phiction、@jessewarren-aa、@ryo-manba、@Yovach 和 @dylanjha。