跳到内容
返回博客

2020年7月27日,星期一

Next.js 9.5

发布者

今天我们很高兴推出 Next.js 9.5,其特点包括

稳定的增量静态再生

Next.js 在 9.3 版本中引入了 静态站点生成 (Static Site Generation) 方法,其明确的目标是:我们应该获得 静态的优势(始终快速、始终在线、全球复制),同时为动态数据提供出色的支持,而这正是 Next.js 的优势所在。

为了兼得两者之长,Next.js 引入了增量静态生成 (Incremental Static Generation),在您构建站点后更新静态内容。通过在 getStaticPaths 中使用 fallback: true 选项,您可以运行时注册新的静态页面

Next.js 可以通过这种方式按需静态预渲染无限数量的页面,无论您的数据集有多大。

今天,我们宣布增量静态再生 (Incremental Static Re-generation)正式可用,这是一种更新现有页面的机制,通过在后台重新渲染页面以响应流量。

受到 stale-while-revalidate 的启发,后台再生确保流量始终从静态存储不间断地提供,并且仅在生成完成后才推送新构建的页面。

export async function getStaticProps() {
  return {
    props: await getDataFromCMS(),
    // we will attempt to re-generate the page:
    // - when a request comes in
    // - at most once every second
    revalidate: 1,
  };
}

revalidate 标志是以秒为单位的时间,在此期间最多发生一次生成,以防止 https://en.wikipedia.org/wiki/Cache_stampede

与传统的 SSR 不同,增量静态再生确保您保留静态的优势

  • 延迟不会出现峰值。页面始终以一致的速度快速提供。
  • 页面永远不会离线。如果后台页面重新生成失败,旧页面将保持不变。
  • 数据库和后端负载低。页面最多并发重新计算一次。

增量功能(添加页面和延迟更新页面)以及 预览模式 现在都已稳定,并且已完全支持 next start 和开箱即用的 Vercel 边缘平台

为了展示这项新功能,我们创建了一个示例,展示了重新生成一个静态页面,该页面显示特定 issue 的各种 GitHub reaction 的计数:https://reactions-demo.vercel.app/

After the first visit following our emoji reaction, a new page generation kicks off in the background. Every single request throughout is served from static cache.
在首次访问并进行 emoji reaction 后,新的页面生成将在后台启动。整个过程中的每个请求都从静态缓存中提供。

接下来,我们将致力于补充 RFC,以解决两个额外的增量静态生成功能

  • 一次重新生成和使多个页面失效(例如您的博客索引和某个博客文章)
  • 通过监听事件(例如 CMS webhook)在用户流量之前进行重新生成

有关更多详细信息,请查看 getStaticProps 文档

可自定义的基础路径

Next.js 项目并非始终从域名的根目录提供服务。有时您可能希望将 Next.js 项目托管在子路径下,例如 /docs,以便 Next.js 项目仅覆盖域名的该子部分。

虽然到目前为止这已经成为可能,但却牺牲了相当多的额外配置。例如,将前缀添加到每个 <Link> 并确保 Next.js 从正确的路径提供 JavaScript 包。

为了解决这个痛点,我们引入了一个新的配置选项。basePath 允许您轻松地在域名的子路径上托管 Next.js 项目。

要开始使用 basePath,您可以将其添加到 next.config.js

next.config.js
module.exports = {
  basePath: '/docs',
};

配置 basePath 后,您的项目将自动从提供的路径路由。在本例中为 /docs

当使用 next/linknext/router 链接到项目中的其他页面时,basePath 将自动添加前缀。这允许您更改 basePath 而无需更改项目。

以下是如何使用 next/link 路由到另一个页面的示例

import Link from 'next/link';
 
export default function HomePage() {
  return (
    <>
      <Link href="/documentation-page">
        <a>Documentation page</a>
      </Link>
    </>
  );
}

以这种方式使用 next/link 将导致以下 HTML 渲染到 Web 浏览器

<a href="/docs/documentation-page">Documentation page</a>

有关更多详细信息,请查看 basePath 文档

支持重写、重定向和标头

重写

在构建 Next.js 项目时,您可能希望将某些路由代理到另一个 URL。例如,如果您想逐步将 Next.js 引入您的堆栈,您将希望路由存在于您的 Next.js 项目中的页面,然后将所有未匹配的内容路由到您正在迁移的旧项目。

在 Next.js 9.5 中,我们引入了一个名为 rewrites 的新配置选项,它允许您将传入的请求路径映射到不同的目标路径,包括外部 URL。

例如,您可能希望将某个路由重写为 example.com

next.config.js
module.exports = {
  async rewrites() {
    return [
      { source: '/backend/:path*', destination: 'https://example.com/:path*' },
    ];
  },
};

在这种情况下,/backend 下的所有路径都将路由到 example.com

您还可以检查您的 Next.js 项目路由是否匹配,如果没有匹配,则重写到之前的项目。这对于 Next.js 的增量采用非常有用

module.exports = {
  async rewrites() {
    return [
      // check if Next.js project routes match before we attempt proxying
      {
        source: '/:path*',
        destination: '/:path*',
      },
      {
        source: '/:path*',
        destination: `https://example.com/:path*`,
      },
    ];
  },
};

在这种情况下,我们首先匹配所有路径。如果没有匹配项,我们将代理到 example.com,这将是之前的项目。

要了解有关 rewrites 功能的更多信息,请查看 rewrites 文档

重定向

大多数网站至少需要一些重定向。尤其是在更改项目路由结构时。例如,当将 /blog 移动到 /news 或类似的转换时。

以前,在您的 Next.js 项目中拥有重定向列表需要设置自定义服务器或自定义 _error 页面来检查路由是否设置了重定向。但是,这以使关键的静态和无服务器优化无效(通过拥有服务器)或不够符合人体工程学为代价。

从 Next.js 9.5 开始,您现在可以在 next.config.jsredirects 键下创建重定向列表

next.config.js
module.exports = {
  async redirects() {
    return [
      {
        source: '/about',
        destination: '/',
        permanent: true,
      },
    ];
  },
};

要了解有关 redirects 功能的更多信息,请查看 redirects 文档

标头

Next.js 允许您构建混合项目,这些项目同时使用静态生成和服务器端渲染。使用服务器端渲染,您可以为传入的请求设置标头。对于静态页面,到目前为止无法设置标头。

我们现在在 next.config.js 中引入了一个 headers 属性,该属性适用于所有 Next.js 路由

next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Feature-Policy',
            // Disable microphone and geolocation
            value: "microphone 'none'; geolocation 'none'",
          },
        ],
      },
    ];
  },
};

headers 选项允许您设置常用的标头,例如 Feature-PolicyContent-Security-Policy

要了解有关 headers 功能的更多信息,请查看 headers 文档

URL 中可选的尾部斜杠

当 3 年前推出 Next.js 时,其默认行为是所有带有尾部斜杠的 URL 始终返回 404 页面。

虽然有效,但一些用户已请求更改此行为的能力。例如,当将现有项目迁移到 Next.js 时,该项目之前始终强制执行尾部斜杠。

在 Next.js 9.5 中,我们为 next.config.js 引入了一个名为 trailingSlash 的新选项。

这个新选项确保 Next.js 自动处理尾部斜杠行为

  • 自动将尾部斜杠 URL 重定向到不带尾部斜杠的 URL,例如:/about//about
  • trailingSlash 设置为 true 时,不带尾部斜杠的 URL 将被重定向到带有尾部斜杠的 URL,例如:/about/about/
  • 确保 next/link 自动应用/删除尾部斜杠,以避免不必要的重定向。
next.config.js
module.exports = {
  // Force a trailing slash, the default value is no trailing slash (false)
  trailingSlash: true,
};

要了解有关 trailingSlash 功能的更多信息,请查看 trailingSlash 文档

页面包的持久缓存

在编写 Next.js 页面时,所有脚本包、CSS 样式表和 HTML 的创建都是完全自动化的,并且与您无关。如果您在 Next.js 9.5 之前检查生成的 <script> 标签,您会注意到它们的 URL 遵循如下模式

/_next/static/ovgxWYrvKyjnlM15qtz7h/pages/about.js

上面的路径段 ovgxWYrvKyjnlM15qtz7h 是我们所说的构建 ID。虽然这些文件很容易在边缘和用户的机器上缓存,但在重新构建您的应用程序后,构建 ID 会更改,并且所有缓存都将被清除。

对于大多数项目来说,这种权衡是可以接受的,但是,我们希望通过不再使未更改页面的浏览器缓存失效来进一步优化此行为。

与 Google Chrome 团队合作开发的 Next.js 9.2 中改进的代码拆分策略 为 Next.js 页面包生成的这些改进奠定了一些基础。

从 Next.js 9.5 开始,所有页面 JavaScript 包都将使用内容哈希而不是构建 ID。这允许在部署之间未更改的页面保留在浏览器和边缘缓存中,而无需再次下载。

相比之下,这些更改后的 URL 模式如下所示

/_next/static/chunks/pages/about.qzfS4o5gIEXRME6sTEahL.js

qzfS4o5gIEXRME6sTEahL 部分不是全局构建 ID,而是 about.js 包的确定性哈希,只要您网站该部分的代码没有更改,它就会保持稳定。此外,它现在通过 Cache-Control: public,max-age=31536000,immutable 在重新部署中长期缓存,Next.js 会自动为您设置。

快速刷新增强

我们在 Next.js 9.4 中引入了快速刷新 (Fast Refresh),这是一种新的热重载体验,可让您即时获得对 React 组件所做编辑的反馈。

Next.js 9.5 进一步改进了我们的快速刷新实现,并为您提供了成功所需的工具

  • 易于理解的错误:所有编译和运行时错误都已更新为 仅显示相关信息,包括导致错误的代码的代码框
  • 开发时保持组件状态的提示:Next.js 现在为您提供有用的提示,以确保快速刷新尽可能在更多场景中保持您的组件状态。Next.js 提供的每个提示都是完全可操作的,并附带前后示例!
  • 组件状态重置时的警告:当 Next.js 无法在编辑文件后保持组件状态时,我们现在将打印详细的警告。此警告将帮助您诊断项目为何必须重置组件状态,从而使您可以修复它并充分利用快速刷新。
  • 新文档:我们添加了大量文档,解释了什么是快速刷新、它的工作原理以及期望什么!该文档还将教您如何通过解释其错误恢复的工作原理来更好地利用快速刷新。
  • 用户代码故障排除指南:新文档还包括关于如何在开发中充分利用快速刷新的 常见故障排除步骤和提示

生产环境 React 性能分析

React 在一段时间前引入了 Profiler API,它允许您跟踪 React 组件中的性能问题。虽然此功能在开发中可以自动工作,但它需要使用单独版本的 ReactDOM 才能在生产环境中进行性能分析。

在 Next.js 9.5 中,您现在可以使用 next build 中的 --profile 标志为 React 启用生产环境性能分析

next build --profile

之后,您可以像在开发中一样使用性能分析器。

要了解有关 React 性能分析的更多信息,您可以阅读 React 团队关于 React 性能分析器的帖子。特别感谢 TODOrTotev@darshkpatel 为此功能做出的贡献。

可选的全部捕获路由

Next.js 9.2 添加了 对全部捕获动态路由的支持,该功能已被社区广泛采用,用于各种用例。全部捕获路由使您可以灵活地创建由无头 CMS、GraphQL API、文件系统等驱动的高度动态的路由结构。

在听取反馈后,我们了解到用户希望在匹配路由的最根层级方面拥有更大的灵活性。今天,我们很高兴为这些高级场景推出可选的全部捕获动态路由

要创建可选的全部捕获路由,您可以使用 [[...slug]] 语法创建一个页面。

例如,pages/blog/[[...slug]].js 将匹配 /blog,以及其下的任何路由,例如:/blog/a/blog/a/b/c 等等。

与全部捕获路由一样,slug 将在 路由器查询对象 中以路径部分数组的形式提供。因此,对于路径 /blog/foo/bar,查询对象将为 { slug: ['foo', 'bar'] }。对于路径 /blog,查询对象将省略 slug 键:{ }

您可以在我们的文档中了解有关可选的全部捕获路由的更多信息

Webpack 5 支持(beta 版)

Webpack 5 目前处于 beta 版。它包括一些重大改进

今天我们很高兴地宣布 Next.js 的 webpack 5 Beta 版本已发布。

要试用 webpack 5,您可以使用 Yarn resolutions 在您的 package.json

package.json
{
  "resolutions": {
    "webpack": "^5.0.0-beta.30"
  }
}

Webpack 5 Beta 版本已在生产环境中的 nextjs.orgvercel.com 上推出。我们鼓励您以渐进的方式试用,并在 GitHub 上反馈您的发现。

编译基础设施改进

为了支持 webpack 5,我们重写了大量的编译管道,使其更贴合 Next.js

  • Next.js 不再依赖 webpack-hot-middlewarewebpack-dev-middleware,而是直接使用 webpack 并专门为 Next.js 项目进行优化。这转化为更简单的架构和更快的开发编译速度。
  • On-demand-entries,按需入口 (On-demand-entries) 是 Next.js 用于在开发期间允许其在您访问的页面上进行编译的系统,也已重写,并且通过利用专门为我们的用例量身定制的新的 webpack 行为,现在更加可靠。
  • React Fast Refresh 和 Next.js Error Overlay 现在与 webpack 5 完全兼容
  • 磁盘缓存将在未来的 Beta 版本中启用。

向后兼容性

我们始终致力于确保 Next.js 与以前的版本向后兼容。

Webpack 4 将继续获得完全支持。我们正在与 webpack 团队紧密合作,以确保从 webpack 4 到 5 的迁移尽可能顺利。

如果您的 Next.js 项目没有自定义 webpack 配置,则无需进行项目更改即可完全利用 webpack 5。

重要提示: 如果您的项目有 自定义 webpack 配置,则可能需要进行一些更改才能过渡到 webpack 5。我们建议您关注我们的迁移说明,或者尽量减少 webpack 扩展的使用,以便将来无缝升级。

改进了 macOS 上的文件监听

我们最近发现 webpack 存在一个问题,在 macOS 上,对代码进行少量更改后,文件监听会停止。您必须手动重启项目才能再次看到更新。几次更改后,该循环会重复出现。

此外,我们发现这个问题不仅发生在 Next.js 项目中,而且发生在所有基于 webpack 构建的项目和框架中。

经过几天的调试,我们追踪到问题的根源在于 webpack 使用的文件监听实现 chokidar,chokidar 是 Node.js 生态系统中广泛使用的文件监听实现。

我们向 chokidar 发送了一个补丁 来修复此问题。在补丁发布后,我们与 Tobias Koppers 合作,在 新版本的 webpack 中推出了此补丁。

当您升级到 Next.js 9.5 时,将自动使用此修补后的 webpack 版本。

结论

我们很高兴看到 Next.js 的采用持续增长

  • 我们已经有超过 1,200 名独立贡献者,自 9.4 版本发布以来新增了超过 135 名贡献者。
  • 在 GitHub 上,该项目已被 star 超过 51,100 次。

加入 GitHub Discussions 上的 Next.js 社区。Discussions 是一个社区空间,您可以在其中与其他 Next.js 用户联系,并自由提问或分享您的作品。

例如,您可以从 与大家分享您的项目 URL 开始。

如果您想回馈社区但又不确定如何做,我们鼓励您尝试实验性功能,例如我们的 Webpack 支持,并反馈您的发现!

鸣谢

我们感谢我们的社区,包括所有帮助塑造此版本的外部反馈和贡献。

特别感谢 Jan Potoms,他是 Next.js 社区的长期成员,为本版本的多个功能做出了贡献。

特别感谢 Tobias Koppers,webpack 的作者,他帮助在 Next.js 中实现了 webpack 5 的支持。

此版本由以下贡献者共同完成:@chandan-reddy-k, @Timer, @aralroca, @artemisart, @sospedra, @prateekbh, @Prioe, @Janpot, @merceyz, @ijjk, @PavelK27, @marbiano, @MichelleLucero, @thorsten-stripe, @TODOrTotev, @Skn0tt, @lfades, @timneutkens, @akhila-ariyachandra, @chibicode, @rafaelalmeidatk, @kirill-konshin, @jamesvidler, @JeffersonBledsoe, @tylev, @jamesmosier, @filipemarins, @Remeic, @vvo, @timothyis, @jazibsawar, @coetry, @adam-zacharski, @danwilliams, @tywmick, @matamatanot, @goldins, @mvllow, @its-tayo, @sshyam-gupta, @wilbert-abreu, @sebastianbenz, @jaydenseric, @developit, @dylanjha, @darshkpatel, @spinks, @stefanprobst, @moh12594, @jasonmerino, @cristiand391, @HyunSangHan, @mcsdevv, @M1ck0, @hydRAnger, @alexej-d, @valmassoi, @motleydev, @eKhattak, @jpedroschmitz, @JerryGoyal, @bowen31337, @phillip055, @balazsorban44, @chuabingquan, @youhosi, @andresz1, @bell-steven, @areai51, @Wssn, @ndom91, @anthonyshort, @zxzl, @jbowes, @IamLizu, @PascalPixel, @ralphilius, @ysun62, @muslax, @elsigh, @AsherFoster, @botv, @tomdohnal, @christianalfoni, @tomasztunik, @gsimone, @illuminist, @jplew, @OskarKaminski, @RickyAbell, @steph-query, @ericgoe, @MalvinJay, @cristianbote, @Ashikpaul, @jensmeindertsma, @amorriscode, @abhik-b, @awareness481, @LukasPolak, @arvigeus, @romMidnight, @jackyef, @drumm2k, @kuldeepkeshwar, @bogy0, @Belco90, @wawjr3d, @tanmaylaud, @SarKurd, @kevinsproles, @dstotijn, @styfle, @blackwright, @BrunoBernardino, @heyAyushh, @Necmttn, @TrySound, @obedparla, @NyashaNziramasanga, @tonyspiro, @kukicado, @ceorourke, @MehediH, @robintom, @karlhorky, and @tcK1!