跳至内容
返回博客

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

使用页面路由器,布局无法组合,数据获取也无法与组件共存。使用新的 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>
  );
}

使用页面路由器,_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。

此组件封装了可以在应用程序中的任何位置重用和组合的逻辑。与文件系统路由配对,这意味着一种简单的方法来开始构建 React 应用程序,感觉就像编写 JavaScript 和 HTML 一样。

例如,如果您想获取一些数据,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 提供了 polyfill,因此您无需导入 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 包中。客户端组件 默认情况下会自动进行代码分割(在 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 (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 中试用Turbopack beta 版,使用 next dev --turbo

服务器操作 (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 Router 和 Pages Router 之间切换。

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

Pages Router 会消失吗?

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

在生产环境中同时使用 pages/app/ 是受支持的,并且鼓励这样做。App Router 可以按 路由级别 采用。

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

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

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

社区

Next.js 是 2600 多位独立开发人员、谷歌和 Meta 等行业合作伙伴以及我们在 Vercel 的核心团队共同努力的结果。加入社区,访问 GitHub 讨论RedditDiscord

此版本由以下人员提供

以及以下人员的贡献:@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。