跳到内容
返回博客

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 Server ComponentsSuspense 的基础上。

这个基础使我们能够删除最初为扩展 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>
  );
}

构建新的文件系统路由器的机会也是解决我们路由系统中许多其他相关功能请求的合适时机。例如

  • 以前,您只能从 _app.js 中的外部 npm 包(如组件库)导入全局样式表。这是一种不太理想的开发者体验。使用 App Router,您可以在任何组件中导入(和并置)任何 CSS 文件。
  • 以前,选择使用 Next.js 进行服务器端渲染(通过 getServerSideProps)意味着在整个页面完成 hydration 之前,与您的应用程序的交互将被阻止。使用 App Router,我们重构了架构以与 React Suspense 深度集成,这意味着我们可以选择性地 hydration 页面的部分内容,而不会阻止 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 架构(包括 Server Components)实现我们的目标带来了机会。

使用 App Router,您可以使用熟悉的 asyncawait 语法获取数据。无需学习新的 API。默认情况下,所有组件都是 React Server Components,因此数据获取在服务器上安全地进行。例如

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 '...';
}

至关重要的是,“数据获取由开发者决定”的原则得以实现。您可以获取数据并组合任何组件。不仅是第一方组件,而且是 Server Components 生态系统中的任何组件,例如 Twitter 嵌入 react-tweet,它被设计为与 Server Components 集成并在服务器上完全运行。

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

此外,路由器将页面导航标记为transitions,使路由过渡可以被中断。

自动服务器渲染和代码拆分

当我们创建 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,Server Components 不包含在浏览器的 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 (Server Component)
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 (Server Component)
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 (Server Component)
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 Components 一样,我们对客户端和服务器的可组合原语的新时代感到兴奋。

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 通道之上,该通道现在已准备好供框架采用 Server Components 等功能。了解更多

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

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

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

Pages Router 会消失吗?

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

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

这是否意味着 Server Components 已经“完成”?

Next.js 是选择基于 React 架构(包括 Server Components)构建的框架之一。我们希望 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。