Back返回博客

Next.js 8

Next.js 8 引入了无服务器模式 (Serverless Mode)、更小的打包体积、性能优化等新特性。

今天我们自豪地推出生产就绪的 Next.js 8,主要特性包括:

一如既往,我们确保所有这些改进都完全向后兼容。对于大多数 Next.js 应用,只需运行:

终端
npm i next@latest react@latest react-dom@latest

我们感谢社区和所有支持我们的人。自上次博客发布以来,我们看到 AT&T星巴克Twitch 等公司使用 Next.js 重构了他们的对外网站和应用。

无服务器 Next.js

Next.js 的无服务器构建目标会将页面输出为无服务器函数 (Serverless Functions)

无服务器部署通过将应用拆分为更小的部分(也称为 lambdas)显著提高了可靠性和可扩展性。在 Next.js 中,pages 目录下的每个页面都会成为一个无服务器函数。

无服务器架构有诸多优势。虽然参考链接中讨论的是 Express 场景,但这些原则具有普适性:无服务器架构支持分布式故障点、无限扩展,并且采用"按使用付费"模式非常经济。

要在 Next.js 中启用无服务器模式,在 next.config.js 中添加 serverless 构建目标:

next.config.js
module.exports = {
  target: 'serverless',
};

serverless 目标会为每个页面输出独立的 lambda 函数。这些文件完全独立,无需任何依赖即可运行:

  • pages/index.js => .next/serverless/pages/index.js
  • pages/about.js => .next/serverless/pages/about.js

Next.js 无服务器函数的签名与 Node.js HTTP 服务器回调类似:

type Function = (req: http.IncomingMessage, res: http.ServerResponse) => void;

Next.js 为无服务器部署提供了底层 API,因为不同托管平台的函数签名各异。通常你需要用兼容层包装 Next.js 无服务器构建的输出。

例如,如果平台支持 Node.js 的 http.Server 类:

server.js
const http = require('http');
const page = require('./.next/serverless/about.js');
const server = new http.Server((req, res) => page.render(req, res));
server.listen(3000, () => console.log('Listening on http://localhost:3000'));

总结

  • 提供实现无服务器部署的底层 API
  • pages 目录下的每个页面都会成为无服务器函数 (lambda)
  • 生成尽可能小的无服务器函数(基础 zip 包仅 50 KB
  • 针对函数的快速冷启动进行了优化
  • 无服务器函数零依赖(所有依赖已打包进函数)
  • 使用 Node.js 的 http.IncomingMessagehttp.ServerResponse
  • 通过 next.config.js 中的 target: 'serverless' 启用
  • 仍完全支持并维护 server 目标
  • 无服务器模式下不支持 publicRuntimeConfigserverRuntimeConfig,请改用构建时配置

大幅减少构建时内存占用

我们为 webpack 贡献了代码,以改进 Next.js(以及整个 webpack 生态)的构建性能和资源利用率。

这些优化使得内存使用量最高可减少 16 倍,且性能不受影响。

内存释放更快,在多页面场景下进程也不再崩溃。

我们将在 Next.js 博客详细介绍这些优化实现。

构建时环境配置

在审查 Next.js 应用时,我们经常发现开发者会添加 babel-plugin-transform-definewebpack.DefinePlugin 来为应用提供配置值。

Next.js 8 在 next.config.js 中引入了新的 env 键,以向后兼容的方式提供相同功能:

next.config.js
module.exports = {
  env: {
    customKey: 'MyValue',
  },
};

这允许你在代码中使用 process.env.customKey。例如:

pages/index.js
export default function IndexPage() {
  return <h1>customKey 的值为: {process.env.customKey}</h1>;
}

process.env.customKey 会在构建时被替换为 'MyValue'

预取性能优化

Next.js 路由允许预取页面以实现更快导航:

pages/index.js
import Link from 'next/link';
 
export default function IndexPage() {
  return (
    <>
      <Link href="/about" prefetch>
        <a>前往关于页面</a>
      </Link>
    </>
  );
}

其工作原理是预取带有 prefetch 属性的链接对应的 JavaScript 包。

在 Next.js 8 之前版本中,这会通过向文档 <body> 注入 <script> 标签实现。

但这会导致页面打开时产生额外开销,最明显的是即使页面已可交互,浏览器"加载"指示仍会显示较长时间。

在 Next.js 8 中,prefetch 改用 <link rel="preload"> 而非 <script> 标签,并且仅在 onload 后开始预取,以便浏览器管理资源。

此外,Next.js 现在会检测 2G 网络和 navigator.connection.saveData 模式,在慢速网络连接下禁用预取。

更小的初始 HTML 体积

由于 Next.js 预渲染 HTML,它会将页面包裹在包含 <html><head><body> 和渲染页面所需 JavaScript 文件的默认结构中。

Next.js 7 我们将初始负载优化至 1.50KB,比前一版本减少了 7.4%。

现在我们进一步将初始负载减小至 1.16KB,再次减少 23%:

7.08.0差异
文档大小(服务端渲染)1.50KB1.16KB23% 减小

主要优化方式包括:

  • 移除了页面初始化内联脚本
  • 不再在每次页面加载时包含 /_error 页面

按需加载 /_error

当生产环境发生错误时,会渲染 /_error 页面显示错误信息。

自 Next.js 首个版本起,/_error 页面的脚本标签就是初始 HTML 的一部分,意味着即使没有运行时错误也会加载。

从 Next.js 8 开始,/_error 页面会在错误发生时按需加载。

这意味着默认情况下需要加载、解析和执行的代码更少。

开发者体验改进

Next.js 的主要目标之一是在提供最佳生产性能的同时,提供最佳的开发者体验。此版本包含许多基于用户反馈的细微改进。

改进的按需条目加载

开箱即用,Next.js 会自动编译仅正在开发的页面。运行 next dev 时,Next.js 不会每次都编译 pages 目录中的所有页面,而是按需编译。

例如,当访问 http://localhost:3000/my-page 时,pages/my-page.js 文件会被按需编译,然后渲染页面。

这确保开发者在启动开发服务器时无需等待所有页面编译完成,这在大型应用中可能耗时较长。它保持内存使用量低且编译器快速,因为打包时编译器不需要考虑所有页面。

按需条目加载流程

按需条目加载流程

当页面超过 25 秒未被访问时,它会被从编译器的构建缓存中清除,以保持编译器快速并减少内存使用。

Next.js 通过轮询机制跟踪页面访问。每 5 秒发送一次"on-demand-entries-ping",让 Next.js 开发服务器知道某个页面正在被访问。

自该功能首次发布以来,轮询是通过 window.fetch 调用实现的,意味着每次触发轮询时,都会在浏览器开发者工具的 consolenetwork 标签中显示。

最常被请求的功能之一就是隐藏这些请求,因为它们会带来不必要的干扰。

我们很高兴地宣布,在 Next.js 8 中,基于 fetch 的轮询已被 WebSocket 方案取代,意味着轮询仍在进行,但仅在检查 WebSocket 连接时可见。

特别感谢 JJ Kasper 协助完成向 WebSocket 的转换。

开发模式下更快的端口监听

启动 Next.js 开发服务器时,需要运行一些初始编译才能处理请求。默认情况下,Next.js 会等待此编译步骤完成后再启动 HTTP 服务器,意味着如果你运行 next dev 后立即打开浏览器,有时可能会看到"无法访问此网站"的消息,因为 HTTP 服务器尚未开始监听连接。

在 Next.js 8 中,HTTP 服务器会在编译开始前就开始监听连接,意味着如果你在编译完成前访问 http://localhost:3000/,请求会等待初始编译完成后再响应,而不需要反复刷新页面直到可用。

特别感谢 Brian Beck 实现此功能。

更快的静态导出

Next.js 专注于预渲染作为实现高性能的手段。预渲染有两种形式:

  • 服务端渲染:每个请求触发一次渲染。最终用户无需等待任何 JS 下载即可开始消费数据
  • 静态渲染:输出可直接服务的静态文件,无需在服务器上执行任何代码

从 Next.js 8 开始,如果你的机器有多个 CPU,通过 next export 进行的静态渲染速度会更快。

基于 4 核 MacBook 的测试显示,通过利用所有核心预渲染页面,导出速度从每秒约 25 页提升至每秒 75 页。

Next.js 会自动检测 CPU 核心数并相应分配页面,无需任何代码变更。

特别感谢 Benjamin Kniffler 实现此功能。

Head 元素去重

构建应用时的常见需求是更新页面的 <head> 元素。例如设置响应式设计的 <title><meta name="viewport">

Next.js 提供了内置组件来修改 <head>

pages/index.js
import Head from 'next/head';
 
export default function IndexPage() {
  return (
    <>
      <Head>
        <title>我的页面标题</title>
      </Head>
    </>
  );
}

<Head> 组件甚至可以在不同组件中多次使用,例如你的布局组件可以设置一些默认的 head 标签。

但你可能想用不同的值覆盖默认 head 标签,在旧版 Next.js 中这会导致输出中出现重复标签,因为无法去重。

因此,现在可以为 <Head> 组件中的每个元素提供 key 属性,这将自动去重具有相同 key 值的标签。

当两个标签都设置 key="viewport" 时,只有最后一个会被渲染。

pages/index.js
import Head from 'next/head';
export default function IndexPage() {
  return (
    <>
      <Head>
        <title>我的页面标题</title>
        <meta
          name="viewport"
          content="initial-scale=1.0, width=device-width"
          key="viewport"
        />
      </Head>
      <Head>
        <meta
          name="viewport"
          content="initial-scale=1.2, width=device-width"
          key="viewport"
        />
      </Head>
    </>
  );
}

安全性改进

新增 crossOrigin 配置选项

在 Next.js 6 中,我们引入了在 pages/_document.js 中为 <Head><NextScript> 添加 crossOrigin 属性的选项,但这并未涵盖设置 cross-origin 的所有用例。

Next.js 有一个客户端路由,会动态注入 <script> 标签,这些标签在注入时缺少 cross-origin 属性。

为确保所有 <script> 标签都设置了 cross-origin 属性,我们在 next.config.js 中引入了新的配置选项:

next.config.js
module.exports = {
  crossOrigin: 'anonymous',
};

引入此选项的另一个好处是,你的应用不再需要自定义 pages/_document.js 来设置 cross-origin

旧行为仍受支持,但会在开发时发出警告,以帮助开发者迁移到新引入的选项。

移除内联 JavaScript

在使用 Next.js 7 及以下版本时,为了启用 内容安全策略 (CSP),用户必须在策略中包含 script-src 'unsafe-inline',因为 Next.js 会创建一个内联的 <script> 标签来传递数据,例如将 getInitialProps 的结果传递到客户端。

从 Next.js 8 开始,我们将这个内联脚本标签改为了 JSON 标签,以便安全地传输到客户端。这意味着 Next.js 不再包含任何内联脚本。

经过慎重考虑,现在可以使用 script-src 'self' 了。

API 认证示例

最常被请求的示例之一是如何在 Next.js 中针对外部 API(任何编程语言的任何 API)进行认证。

随着 Next.js 8 的发布,我们还引入了一个新创建的示例:with-cookie-auth

这个示例展示了如何针对一个外部的 Node.js API 进行认证,但所应用的概念适用于任何无状态的 API。

该示例使用 cookie 在服务端渲染和客户端渲染之间共享令牌。

这样,如果应用在服务端渲染,它仍然可以代表用户获取认证数据。

特别感谢贡献此示例的 Juan Olvera

社区

自首次发布以来,Next.js 已被用于从财富 500 强公司到个人博客的各类场景。我们非常高兴看到 Next.js 的采用率持续增长。

  • 已有超过 600 名贡献者 提交了至少 1 次提交。
  • 在 GitHub 上,该项目已获得超过 34,400 次星标
  • 自首次发布以来,已提交了超过 2600 个拉取请求

Next.js 社区拥有超过 4,570 名成员加入我们吧!