跳到内容
返回博客

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 应用程序的方式,我们的目标是创建一个更动态、个性化和全球化的 Web。

在最初的公告帖子中,我们分享了 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 路由器来说并不容易进行改造。

框架的每个部分都必须围绕路由器进行设计。页面过渡、数据获取、缓存、修改和重新验证数据、流式处理、样式化内容等等。

为了使我们的路由器与流式处理兼容,并解决这些对增强布局支持的需求,我们着手构建新版本的路由器。

这就是我们在最初发布 Layouts 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 的功能对等性。我们正在 此 issue 中跟踪对这些功能的支持。但是,现在应该支持大多数用例。我们此 beta 版的目标是继续解决因采用率增加而产生的剩余错误,并为未来版本的稳定性做好准备。

我们对改进 Turbopack 的增量引擎和缓存层的投入不仅将加快本地开发速度,而且还将很快加快生产构建速度。请继续关注未来的 Next.js 版本,您将能够在其中运行 next build --turbo 以实现即时构建。

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

Server Actions (Alpha 版)

React 生态系统已经看到了围绕表单、管理表单状态以及缓存和重新验证数据的许多创新和想法探索。随着时间的推移,React 在某些模式上变得更加有主见。例如,推荐用于表单状态的 “非受控组件”

当前生态系统的解决方案要么是可重用的客户端解决方案,要么是构建到框架中的原语。到目前为止,还没有一种方法可以组合服务器突变和数据原语。React 团队 一直在开发第一方突变解决方案。

我们很高兴地宣布在 Next.js 中支持实验性的 Server Actions,使您能够在服务器上修改数据,直接调用函数,无需创建中间 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>
  );
}

使用 Server Actions,您将拥有强大的服务器优先数据突变、更少的客户端 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 中的 Server Actions 旨在与数据生命周期的其余部分深度集成,包括 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();
  // ...
}

Server Actions 被设计为可组合的。React 社区中的任何人都可以构建和发布 Server Actions,并在生态系统中分发它们。就像服务端组件一样,我们对客户端和服务器的可组合原语的新时代感到兴奋。

Server Actions 今天在 Next.js 13.4 中以 alpha 版发布。通过选择使用 Server Actions,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 早期采用者的验证。

未来我们仍然希望进行额外的优化,包括 Server Actions 达到完全稳定。对我们来说,重要的是推动核心稳定性,以帮助社区明确他们今天应该从哪里开始学习和构建应用程序。

App Router 构建在 React canary 通道之上,该通道现在已准备好用于框架采用服务端组件等功能。了解更多

这对 Next.js beta 文档意味着什么?

从今天开始,我们建议使用 App Router 构建新的应用程序。Next.js beta 文档,一直用于解释 App Router 并从头开始重写,现在已合并回稳定的 Next.js 文档。您现在可以轻松地在 App Router 或 Pages Router 之间切换。

我们建议阅读App Router 增量采用指南,以了解如何采用 App Router。

Pages Router 会消失吗?

不会。我们致力于支持 pages/ 开发,包括错误修复、改进和安全补丁,以便在未来的多个主要版本中向前发展。我们希望确保开发者有足够的时间在他们准备好时逐步采用 App Router。

支持并鼓励在生产环境中同时使用 pages/app/。App Router 可以按路由逐步采用。

这是否意味着服务端组件“已完成”?

Next.js 是选择基于 React 架构(包括服务端组件)构建的框架之一。我们希望 App Router 提供的体验能够鼓励其他框架(或新框架)也考虑使用此架构。

在这个生态系统中,仍然有一些模式尚未定义,例如处理无限滚动。目前,我们建议在生态系统发展和库创建或更新时,对这些模式使用客户端解决方案。

社区

Next.js 是 2600 多名个人开发者、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, and @dylanjha。