跳到内容
返回博客

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 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,布局无法组合,并且数据获取无法与组件 colocated。使用新的 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 用于自定义服务器的初始 payload。

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,您可以在任何组件中导入(并 colocated)任何 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 改进,它填充了 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 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 '...';
}

至关重要的是,“数据获取由开发者决定”的原则得以实现。您可以获取数据并组合任何组件。而且不仅仅是第一方组件,而是服务器组件生态系统中的任何组件,例如 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 bundle 中。客户端组件默认自动进行代码分割(使用 Next.js 中的 webpack 或 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(测试版)

Turbopack,我们正在通过 Next.js 测试和稳定版的新打包工具,有助于在使用 Next.js 应用程序(通过 next dev --turbo)时加快本地迭代速度,并很快应用于生产构建(next build --turbo)。

自 Next.js 13 发布 alpha 版本以来,随着我们致力于修补错误并添加对缺失功能的支持,我们看到了采用率的稳步增长。我们一直在 Vercel.com 上进行 Turbopack 的内部测试,并与许多运营大型 Next.js 网站的 Vercel 客户合作,以收集反馈并提高稳定性。我们感谢社区在测试和向我们的团队报告错误方面给予的支持。

六个月后,我们已准备好进入 beta 阶段。

Turbopack 尚未完全支持 webpack 和 Next.js 的所有功能。我们正在此问题中跟踪对这些功能的支持。但是,大多数用例现在都应该得到支持。我们在此 beta 版中的目标是继续解决由于采用率增加而产生的剩余错误,并为未来版本的稳定性做准备。

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

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

服务器 Actions (Alpha)

React 生态系统在表单、管理表单状态以及数据缓存和重新验证方面看到了许多创新和探索。随着时间的推移,React 对其中一些模式变得更加规范。例如,推荐用于表单状态的“非受控组件”

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

我们很高兴地宣布支持 Next.js 中的实验性服务器 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>
  );
}

通过服务器操作,您可以实现强大的服务器优先数据修改、更少的客户端 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();
  // ...
}

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

服务器 Actions 在 Next.js 13.4 中以 alpha 版提供。通过选择使用服务器 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 早期采用者进行了验证。

未来我们仍将进行额外的优化,包括服务器操作达到完全稳定。对我们来说,推动核心稳定性以帮助社区明确他们今天应该从何处开始学习和构建应用程序至关重要。

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。