Back返回博客

Next.js 7 正式发布

经过 26 个 canary 版本迭代和 340 万次下载后,我们自豪地推出生产就绪的 Next.js 7

经过 26 个 canary 版本迭代和 340 万次下载后,我们自豪地推出生产就绪的 Next.js 7,主要特性包括:

值此之际,我们很高兴通过全新改版的 Nextjs.org 与您分享这些更新

开发者体验优化

Next.js 的核心目标之一是:在提供最佳生产性能的同时,打造极致的开发者体验。本次版本对构建和调试流程进行了多项重要改进

编译速度

得益于 webpack 4、Babel 7 以及代码库的多项优化,Next.js 开发环境启动速度最高提升 57%。

通过引入增量编译缓存机制,代码变更后的构建速度可提升 40%。

以下是我们收集的基准测试数据:

6.07.0差异
基础应用启动时间1947ms835ms57% 提升
基础应用代码变更304ms178ms42% 提升

额外福利:得益于 webpackbar,您现在能在开发和构建过程中获得更直观的实时反馈:

React 错误覆盖层增强错误报告

准确且友好的错误提示对开发和调试体验至关重要。此前我们仅展示错误信息和堆栈跟踪,现在通过集成 react-error-overlay,堆栈跟踪将包含:

  • 服务端与客户端错误的精确定位
  • 源码高亮提供上下文
  • 完整的增强型堆栈跟踪

下图展示了错误提示的对比改进:

左侧为旧版错误提示,右侧为 react-error-overlay

额外福利:通过 react-error-overlay,点击代码块即可快速在编辑器中打开对应文件。

Webpack 4

自首个版本起,Next.js 就采用 webpack 进行代码打包,并复用其丰富的插件生态。我们很高兴宣布 Next.js 现已升级至最新的 webpack 4,带来诸多改进和错误修复。

主要升级包括:

  • 支持 .mjs 源文件
  • 代码分割优化
  • 更完善的 tree-shaking(无用代码消除)支持

新特性还包括 WebAssembly 支持,Next.js 甚至能服务端渲染 WebAssembly,参见此示例

注意: 本次升级完全向后兼容。但如果您通过 next.config.js 使用自定义 webpack 加载器或插件,可能需要相应升级。

CSS 导入

webpack 4 引入了新的 CSS 提取方案 mini-extract-css-plugin

@zeit/next-css@zeit/next-less@zeit/next-sass@zeit/next-stylus 现已基于 mini-extract-css-plugin 重构。

新版插件解决了 20 个与 CSS 导入相关的问题,例如现在支持在动态 import() 中导入 CSS:

components/my-dynamic-component.js
import './my-dynamic-component.css';
 
export default function MyDynamicComponent() {
  return <h1>My dynamic component</h1>;
}
pages/index.js
import dynamic from 'next/dynamic'
 
const MyDynamicComponent = dynamic(import('../components/my-dynamic-component'))
 
export default function Index() {
  return () {
    <div>
      <MyDynamicComponent/>
    </div>
  }
}

重大改进是您不再需要在 pages/_document.js 中添加以下代码:

<link rel="stylesheet" href="/_next/static/style.css" />

Next.js 现在会自动注入 CSS 文件。在生产环境中,还会自动为 CSS URL 添加内容哈希,确保用户永远不会获取过时文件,并支持永久不可变缓存。

简言之,要在 Next.js 项目中支持导入 .css 文件,只需在 next.config.js注册 withCSS 插件

const withCSS = require('@zeit/next-css');
module.exports = withCSS({
  /* my next config */
});

标准化动态导入

自版本 3 起,Next.js 就通过 next/dynamic 支持动态导入。

作为该技术的早期采用者,我们当时不得不自行实现 import() 处理方案。

这导致 Next.js 逐渐与 webpack 后续引入的原生支持产生差异,某些功能缺失。

因此 Next.js 之前无法支持 webpack 后来引入的部分 import() 特性。

例如无法手动指定分块名称和捆绑文件:

import(/* webpackChunkName: 'my-chunk' */ '../lib/my-library');

另一个例子是无法直接使用 import() 而必须通过 next/dynamic 模块包装。

从 Next.js 7 开始,我们不再修改默认的 import() 行为,这意味着您能开箱即用地获得完整的 import() 支持

此变更同样完全向后兼容。动态组件的使用方式依然简单:

pages/index.js
import dynamic from 'next/dynamic';
 
const MyComponent = dynamic(import('../components/my-component'));
 
export default function Index() {
  return (
    <div>
      <MyComponent />
    </div>
  );
}

此示例会为 my-component 创建独立的 JavaScript 文件,仅在渲染 <MyComponent /> 时加载。

最重要的是,如果组件未被渲染,其 <script> 标签不会出现在初始 HTML 文档负载中

为了简化代码库并利用 React 生态优势,Next.js 7 的 next/dynamic 内部重构为基于 react-loadable(略有修改)。这还引入了两项动态组件新特性:

  • 通过 next/dynamictimeout 选项设置超时
  • 通过 delay 选项设置加载组件延迟时间。例如当导入速度极快时,可延迟显示加载状态

Babel 7

Next.js 6 在 Babel 7 尚处测试阶段时就已支持。如今 Babel 7 稳定版已发布,Next.js 7 也完成了升级。

完整变更请参阅 Babel 发布说明

主要特性包括:

  • TypeScript 支持(可通过 @zeit/next-typescript 实现)
  • 支持 Fragment 语法 <>
  • 支持 babel.config.js
  • 新增 overrides 属性,可针对特定文件或目录应用预设/插件

如果您的 Next.js 项目没有自定义 Babel 配置,则不存在破坏性变更。

若有自定义 Babel 配置,则需要将相关插件/预设升级至最新版本。

若从低于 Next.js 6 的版本升级,可运行优秀的 babel-upgrade 工具。

除升级至 Babel 7 外,Next.js Babel 预设 (next/babel) 现在默认在 NODE_ENV=test 时将 modules 选项设为 commonjs

此前这通常是 Next.js 项目中创建自定义 .babelrc 的唯一原因:

.babelrc
{
  "env": {
    "development": {
      "presets": ["next/babel"]
    },
    "production": {
      "presets": ["next/babel"]
    },
    "test": {
      "presets": [["next/babel", { "preset-env": { "modules": "commonjs" } }]]
    }
  }
}

在 Next.js 7 中简化为:

.babelrc
{
  "presets": ["next/babel"]
}

此时甚至可以移除 .babelrc 文件,因为 Next.js 会在没有 Babel 配置时自动使用 next/babel

更精简的初始 HTML 负载

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

此前初始负载约为 1.62kB。Next.js 7 优化后降至 1.5kB,缩减 7.4%,使页面更轻量。

6.07.0差异
服务端渲染文档大小1.62kb1.50kb7.4% 缩减

主要优化手段包括:

  • 移除 __next-error div
  • 压缩内联脚本(未来版本将完全移除)
  • 编译时消除未使用的 __NEXT_DATA__ 属性(如 nextExportassetPrefix

静态 CDN 支持

Next.js 5 引入了 assetPrefix 支持,使资源能从指定位置(通常是 CDN)自动加载。此功能在 CDN 支持代理时效果最佳,例如请求如下 URL:

https://cdn.example.com/_next/static/<buildid>/pages/index.js

通常 CDN 会检查缓存,若无则从源站请求。

边缘网络 正是采用这种代理机制。

但某些解决方案要求直接将目录预上传至 CDN。问题在于 Next.js 的 URL 结构与 .next 文件夹内部结构不匹配。例如:

https://cdn.example.com/_next/static/<buildid>/pages/index.js
// 对应路径:
.next/page/index.js

Next.js 7 调整了 .next 目录结构以匹配 URL 结构:

https://cdn.example.com/_next/static/<buildid>/pages/index.js
// 对应路径:
.next/static/<buildid>/pages/index.js

虽然我们推荐使用代理型 CDN,但新结构也支持用户将 .next 目录上传至其他类型 CDN。

Styled JSX v3

我们很高兴推出 styled-jsx 3,Next.js 默认集成的 CSS-in-JS 方案现已支持 React Suspense

一个常见问题是:当子组件不属于当前组件作用域时,如何为其添加样式?例如当子组件仅在父组件中需要特定样式时:

pages/index.js
const ChildComponent = () => (
  <div>
    <p>some text</p>
  </div>
);
 
export default function Index() {
  return (
    <div>
      <ChildComponent />
      <style jsx>{`
        p {
          color: black;
        }
      `}</style>
    </div>
  );
}

上述代码尝试选择 p 标签但无效,因为 styled-jsx 样式仅作用于当前组件,不会泄漏到子组件。传统解决方案是使用 :global 方法移除 p 标签前缀,但这会导致样式泄漏到整个页面。

styled-jsx 3 通过引入新 API css.resolve 解决了此问题,它会为给定的 styled-jsx 字符串生成 className<style> 标签(即 styles 属性):

pages/index.js
import css from 'styled-jsx/css';
 
const ChildComponent = ({ className }) => (
  <div>
    <p className={className}>some text</p>
  </div>
);
 
const { className, styles } = css.resolve`
  p {
    color: black;
  }
`;
 
export default function Index() {
  return (
    <div>
      <ChildComponent className={className} />
      {styles}
    </div>
  );
}

此 API 支持透明地将自定义样式传递给子组件。

作为 styled-jsx 的主要版本升级,如果您使用 styles-jsx/css,会有一项破坏性变更以优化包体积。在 styled-jsx 2 中,即使仅使用"scoped"版本,也会同时生成"scoped"和"global"版本的外部样式。

styled-jsx 3 要求全局样式必须通过 css.global 而非 css 标记,以便优化包体积。

完整变更请参阅发布说明

App 与 Pages 间的服务端渲染 React Context

从 Next.js 7 开始,我们支持在 pages/_app.js 和页面组件间使用新版 React context API。

此前在服务端无法在页面间使用 React context。这是因为 webpack 使用内部模块缓存而非 require.cache,我们为此编写了自定义 webpack 插件来共享页面间的模块实例。

此改动不仅支持了新版 React context,还减少了 Next.js 在页面间共享代码时的内存占用。

6.07.0差异
内存占用57.5MB48.1MB16% 降低

nextjs.org

伴随 Next.js 7 发布,我们推出了全新设计的 nextjs.org

博客

您正在阅读的这篇博客文章已收录于 nextjs.org 的新版博客板块。这里将成为 Next.js 相关资讯(如版本发布)的新主场。

全新 nextjs.org

全新 nextjs.org

获取灵感

随着使用 Next.js 的应用数量持续增长,我们需要一种方式来集中展示这些精美的应用。欢迎体验全新的 /showcase 页面:

在 nextjs.org/showcase 获取灵感

在 nextjs.org/showcase 获取灵感

这个全新的展示平台让我们能够持续添加基于 Next.js 构建的新应用。

我们诚邀您访问 /showcase 获取灵感,或提交您使用 Next.js 开发的应用!

社区

自首次发布以来,Next.js 已被广泛应用于从世界 500 强企业到个人博客的各类场景。我们非常欣喜地见证了 Next.js 的普及增长:

  • 目前有超过 12,500 个公开索引的域名正在使用 Next.js
  • 已有超过 500 位贡献者提交了至少 1 次代码提交
  • 该项目在 GitHub 上获得了超过 29,000 次星标
  • 自首次发布以来已收到近 2200 个拉取请求

Next.js 社区拥有近 2000 名成员。立即加入我们!