Back返回博客

Next.js 9.3 版本发布

Next.js 9.3 带来了全新的静态站点生成优化、原生 SCSS 支持、更小的打包体积、静态 404 页面等特性!

今天我们很高兴地发布 Next.js 9.3,主要特性包括:

所有这些改进都是非破坏性的且完全向后兼容。要更新只需运行:

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

新一代静态站点生成 (SSG) 支持

构建网站或 Web 应用时通常需要在两种策略间选择:静态生成 (SSG) 或服务端渲染 (SSR)。

Next.js 是首个混合框架,允许您根据每个页面的具体情况选择最适合的技术。

Next.js 9.0 引入了自动静态优化的概念。当页面没有阻塞性数据获取需求(如 getInitialProps)时,它会在构建时自动渲染为 HTML。

更多情况下,即使有阻塞性数据获取需求,您可能仍希望在构建时将页面渲染为静态 HTML。例如由 (无头) 内容管理系统 (CMS) 驱动的营销页面或网站的博客部分。

我们与 SSG 和 next export 的重度用户(如 HashiCorp)合作,并通过 Next.js 历史上评论最多的 RFC 与社区深入讨论了正确的约束条件,以创建数据获取和静态生成的新统一方式。

今天我们非常激动地宣布两个新的数据获取方法:getStaticPropsgetServerSideProps。我们还提供了一种为动态路由静态生成页面时提供参数的方式:getStaticPaths

这些新方法相比 getInitialProps 模型有许多优势,因为它们在 SSG 和 SSR 之间有明确的区分:

  • getStaticProps(静态生成):在构建时获取数据
  • getStaticPaths(静态生成):基于数据指定要预渲染的动态路由
  • getServerSideProps(服务端渲染):在每次请求时获取数据
  • 这些改进是 API 的补充。所有新功能完全向后兼容并可逐步采用。没有引入弃用,getInitialProps 将继续按当前方式工作。我们鼓励在新页面和项目中采用这些新方法

getStaticProps

如果从页面导出名为 getStaticPropsasync 函数,Next.js 将在构建时预渲染此页面。这在您希望从 CMS 渲染特定静态页面时特别有用。

getStaticProps 始终在 Node.js 环境中运行,代码会自动从浏览器包中进行 tree-shaken,确保发送到浏览器的代码更少。这样您就不必担心数据获取代码在 Node.js 和浏览器环境中的执行不一致问题。

这允许您使用任何异步甚至同步的数据获取技术,包括 fetch、REST、GraphQL 或直接访问数据库。

pages/posts/[id].js
export async function getStaticProps(context) {
  return {
    props: {}, // 将作为 props 传递给页面组件
  };
}

context 参数是包含以下键的对象:

  • params:对于使用动态路由的页面,params 包含路由参数。例如,如果页面名称是 [id].js,则 params 将类似 { id: ... }。更多信息请查看动态路由文档。这应与 getStaticPaths 一起使用,我们稍后会解释。

以下示例使用 getStaticProps 从 CMS 获取博客文章列表:

pages/blog.js
// 可以使用任何数据获取库
import fetch from 'node-fetch';
 
// posts 将在构建时由 getStaticProps() 填充
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}
 
// 此函数在 Node.js 环境的构建时调用
// 不会在客户端调用,因此甚至可以执行
// 直接数据库查询。参见"技术细节"部分
export async function getStaticProps() {
  // 调用外部 API 端点获取文章
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // 通过返回 { props: posts },Blog 组件
  // 将在构建时接收 posts 作为 prop
  return {
    props: {
      posts,
    },
  };
}
 
export default Blog;

何时使用 getStaticProps?

在以下情况下应使用 getStaticProps

  • 渲染页面所需的数据在用户请求前的构建时可用
  • 数据来自无头 CMS
  • 数据可以公开缓存(非用户特定)
  • 页面必须预渲染(为了 SEO)且速度极快 —— getStaticProps 生成 HTML 和 JSON 文件,两者都可以通过 CDN 缓存以提高性能

有关 getStaticProps 的更多信息,请参阅数据获取文档

getStaticPaths

如果页面有动态路由并使用 getStaticProps,则需要定义在构建时需要渲染为 HTML 的路径列表。

如果从使用动态路由的页面导出名为 getStaticPathsasync 函数,Next.js 将静态预渲染 getStaticPaths 指定的所有路径。

pages/posts/[id].js
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // 参见下面的"paths"部分
    ],
    fallback: truefalse // 参见下面的"fallback"部分
  };
}

paths 键(必需)

paths 键决定哪些路径将被预渲染。例如,假设您有一个使用动态路由的页面 pages/posts/[id].js。如果从此页面导出 getStaticPaths 并为 paths 返回以下内容:

return {
  paths: [
    { params: { id: 1 } },
    { params: { id: 2 } }
  ],
  fallback: ...
}

那么 Next.js 将使用 pages/posts/[id].js 中的页面组件在构建时静态生成 posts/1posts/2

注意每个 params 的值必须与页面名称中使用的参数匹配:

  • 如果页面名称是 pages/posts/[postId]/[commentId],则 params 应包含 postIdcommentId
  • 如果页面名称使用全捕获路由,例如 pages/[...slug],则 params 应包含 slug(一个数组)。例如,如果此数组是 ['foo', 'bar'],则 Next.js 将在 /foo/bar 静态生成页面

fallback 键(必需)

getStaticPaths 返回的对象必须包含布尔值 fallback 键。

fallback: false

如果 fallbackfalse,则任何未由 getStaticPaths 返回的路径都将导致 404 页面。这在您知道所有路径都将在构建时已知时很有用。

以下示例预渲染名为 pages/posts/[id].js 的每篇博客文章页面。博客文章列表将从 CMS 获取并由 getStaticPaths 返回。然后,对于每个页面,使用 getStaticProps 从 CMS 获取文章数据。

pages/posts/[id].js
import fetch from 'node-fetch';
 
function Post({ post }) {
  // 渲染文章...
}
 
// 此函数在构建时调用
export async function getStaticPaths() {
  // 调用外部 API 端点获取文章
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // 基于文章获取我们想要预渲染的路径
  const paths = posts.map((post) => `/posts/${post.id}`);
 
  // 我们将在构建时仅预渲染这些路径
  // { fallback: false } 表示其他路由应返回 404
  return { paths, fallback: false };
}
 
// 此函数也在构建时调用
export async function getStaticProps({ params }) {
  // params 包含文章 `id`
  // 如果路由类似 /posts/1,则 params.id 为 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // 通过 props 将文章数据传递给页面
  return { props: { post } };
}
 
export default Post;

fallback: true

如果 fallbacktrue,则 getStaticProps 的行为会发生变化,Next.js 将在构建时将提供的路径渲染为 HTML。当路径未在构建时生成时,将在用户请求页面时按需生成。

这在应用有许多可以静态生成的路由,但您不希望仅生成部分页面而增加构建时间时非常有用。

触发页面生成的用户将收到一个回退 HTML,这通常是一个加载状态的页面。这样做的原因是静态 HTML 可以从 CDN 提供,确保页面始终快速,即使尚未生成。

以下示例展示按需静态生成额外页面的情况:

pages/posts/[id].js
import { useRouter } from 'next/router';
import fetch from 'node-fetch';
 
function Post({ post }) {
  const router = useRouter();
 
  // 如果页面尚未生成,将显示此内容
  // 直到 getStaticProps() 运行完成
  if (router.isFallback) {
    return <div>加载中...</div>;
  }
 
  // 渲染文章...
}
 
// 此函数在构建时调用
export async function getStaticPaths() {
  return {
    // 仅 /posts/1 和 /posts/2 在构建时生成
    paths: [{ params: { id: 1 } }, { params: { id: 2 } }],
    // 启用静态生成额外页面
    // 例如:/posts/3
    fallback: true,
  };
}
 
// 此函数也在构建时调用
export async function getStaticProps({ params }) {
  // params 包含文章 `id`
  // 如果路由类似 /posts/1,则 params.id 为 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // 通过 props 将文章数据传递给页面
  return { props: { post } };
}
 
export default Post;

有关 getStaticPaths 的更多信息,请参阅数据获取文档

getServerSideProps

如果从页面导出一个名为 getServerSidePropsasync 函数,Next.js 会在每次请求时渲染该页面(服务端渲染 SSR)。

getServerSideProps 始终在服务端运行,其代码会自动从浏览器包中进行 tree-shaken 移除,确保更少的代码发送到浏览器。这样您无需担心数据获取代码在服务端和浏览器环境中的执行不一致问题。由于服务器通常与数据源有更快的连接,这在许多情况下能提高性能。同时通过减少数据获取逻辑的暴露,也增强了安全性。

您可以使用任何异步甚至同步的数据获取技术,包括 fetch、REST、GraphQL 或直接访问数据库。

当使用 next/link 在页面间导航时(而非在浏览器中执行 getServerSideProps),Next.js 会向服务器发起获取请求,服务器将返回调用 getServerSideProps 的结果。

pages/index.js
export async function getServerSideProps(context) {
  return {
    props: {}, // 将作为 props 传递给页面组件
  };
}

context 参数是一个包含以下键的对象:

以下示例使用 getServerSideProps 在请求时获取数据并渲染:

pages/index.js
function Page({ data }) {
  // 渲染数据...
}
 
// 每次请求都会调用此函数
export async function getServerSideProps() {
  // 从外部 API 获取数据
  const res = await fetch(`https://.../data`);
  const data = await res.json();
 
  // 通过 props 将数据传递给页面
  return { props: { data } };
}
 
export default Page;

了解更多关于 getServerSideProps 的信息,请参阅 数据获取文档

预览模式

如前文所述,当页面从无头 CMS 获取数据时,静态生成 (Static Generation) 非常有用。但当您在无头 CMS 上撰写草稿并希望立即预览时,静态生成就不理想了。由于输出是静态的,预览变更会变得困难,因为您必须重新生成静态页面。

Next.js 引入的 getStaticProps 开启了新的可能性,例如在某些条件下利用 Next.js 的按需渲染能力。

例如,当预览无头 CMS 的草稿时,您可能希望绕过静态渲染,按需渲染包含草稿内容而非发布内容的页面。您会希望 Next.js 仅针对这种情况绕过静态生成。

我们很高兴宣布 Next.js 的新内置功能:预览模式 (Preview Mode)。

预览模式允许用户绕过静态生成的页面,按需渲染(SSR)来自 CMS 的草稿页面。

但不仅限于特定 CMS 系统。预览模式直接与 getStaticPropsgetServerSideProps 集成,因此可与任何类型的数据获取方案配合使用。

使用 next start 或无缝部署Vercel 边缘网络时,预览模式已可用。

亲自体验预览模式:https://next-preview.vercel.app/

通过参考文档了解更多关于预览模式的信息。

与 CMS 提供商合作

getStaticProps 允许您从任何数据源(包括 CMS 系统)获取数据

我们正积极与 CMS 生态系统中的许多关键参与者合作,提供与 Next.js 集成的示例和指南。

当前正在积极开发的示例包括:

如果您的公司在 CMS 生态系统中活跃,我们很乐意合作!欢迎通过邮件Twitter 联系我们的团队。

全局样式表的内置 Sass 支持

Next.js 9.2 引入了全局 CSS 样式表的内置支持,以取代 next-css 插件,通过更好的默认设置提供更优化的结果。

发布后,我们越来越多地被要求集成 Sass 支持,因为许多迁移到 Next.js 的企业拥有基于 Sass 的现有设计系统。

通过调查 Next.js 插件使用情况,我们发现约 30% 的 Next.js 应用目前使用 next-sass,而 44% 使用原生 CSS,6% 使用 Less。

此外,next-sass 存在与 next-css 相同的约束缺失问题。这意味着您可以在项目的每个文件中导入 Sass 文件,但这些导入的 Sass 文件将对整个应用全局生效。

基于这些统计数据和反馈,我们很高兴地宣布 Next.js 现在内置支持导入 Sass 样式表。

要在应用中使用全局 Sass 导入,请先安装 sass

Terminal
npm install sass

然后在 pages/_app.js 中导入 Sass 文件。

例如,假设项目根目录下有以下名为 styles.scss 的样式表:

$primary-color: #333;
 
body {
  padding: 20px 20px 60px;
  margin: 0;
  color: $primary-color;
}

如果尚未存在,请创建 pages/_app.js 文件。然后导入 styles.scss 文件:

pages/_app.js
import '../styles.scss';
 
// 在新建的 `pages/_app.js` 文件中必须导出此默认函数。
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

由于样式表本质上是全局的,必须在 自定义 <App> 组件中导入,以避免全局样式的类名和顺序冲突。

在开发环境中,这种样式表表达方式允许您在编辑样式时页面自动更新。

在生产环境中,所有 Sass 和 CSS 文件会自动合并为一个经过压缩的 .css 文件。该 CSS 文件将通过 <link> 标签加载,并自动注入到 Next.js 生成的默认 HTML 标记中。

此新功能完全向后兼容。如果您正在使用 @zeit/next-sass 或其他 CSS 相关插件,该功能会被禁用以避免冲突。

如果您当前使用 @zeit/next-sass,建议从 next.config.jspackage.json 中移除该插件,升级后转而使用内置的 Sass 支持。

组件级样式的内置 Sass CSS 模块支持

Next.js 现在支持使用 [name].module.scss 文件命名约定的 Sass 文件的 CSS 模块

与 Next.js 5+ 中通过 next-sass 提供的支持不同,全局 Sass 和 CSS 模块现在可以共存——next-sass 要求应用中所有 .scss 文件要么全局处理,要么局部处理,不能两者兼有。

CSS 模块通过自动创建唯一的类名来实现 Sass 的局部作用域。这允许您在不同的文件中使用相同的 Sass 类名而不必担心冲突。

这种行为使 CSS 模块成为包含组件级 Sass 的理想方式。CSS 模块文件可以在应用的任何位置导入

要在应用中使用 Sass CSS 模块,请确保已安装 sass

Terminal
npm install sass

现在,假设 components/ 文件夹中有一个可复用的 Button 组件:

首先,创建包含以下内容的 components/Button.module.scss

/*
无需担心 .error {} 与其他 `.css` 或 `.module.css` 文件冲突!
*/
$color: white;
 
.error {
  color: $color;
  background-color: red;
}

然后,创建 components/Button.js,导入并使用上述 CSS 文件:

components/Button.js
import styles from './Button.module.scss';
 
export function Button() {
  return (
    <button
      type="button"
      // 注意 "error" 类是作为导入的 `styles` 对象的属性访问的
      className={styles.error}
    >
      销毁
    </button>
  );
}

Sass 文件的 CSS 模块是一个_可选_功能,仅对扩展名为 .module.scss 的文件启用。常规的 <link> 样式表全局 Sass 样式仍然受支持。

在生产环境中,所有 CSS 模块文件会自动合并为多个经过压缩和代码拆分的 .css 文件。这些 .css 文件代表了应用中的热执行路径,确保为每个页面加载最少数量的 CSS 以实现绘制。

与上述相同,此新功能完全向后兼容。如果您正在使用 @zeit/next-sass 或其他 CSS 相关插件,该功能会被禁用以避免冲突。

如果您当前使用 @zeit/next-sass,建议从 next.config.jspackage.json 中移除该插件,转而使用内置的 Sass 支持。

404 页面的自动静态优化

Next.js 9 的发布引入了 自动静态优化 的概念:当页面没有阻塞数据需求时,Next.js 会在构建时自动将页面生成为静态 HTML。然而,有一个页面未被自动渲染为静态 HTML:404 页面。404 页面未自动静态化的主要原因是处理 404 的 /_error 页面不仅处理 404,还处理其他错误。

由于 404 页面针对不存在的路由进行渲染,按需渲染页面可能导致成本增加和服务器负载加重。

我们通过两种方式帮助您获得最佳实践:

  • 默认的 Next.js 体验会生成静态 404 页面
  • 自定义 404 页面时仍确保最终获得静态页面

此功能完全向后兼容,因此如果您当前有自定义的 pages/_error.js,在添加 pages/404.js 之前,它将继续用于 404 页面。

默认的静态 404 页面

当应用没有自定义的 pages/_error.js 页面时,Next.js 会自动静态生成 404 页面,并在需要提供 404 时使用该页面。这一过程自动完成,无需任何更改。

使用 pages/404.js 自定义 404 页面

要覆盖默认 404 页面,您现在可以创建 pages/404.js,该页面仍会在构建时自动静态优化。如果应用有此页面,将使用它而非 pages/_error.js 来渲染 404。

pages/404.js
export default () => <h1>这是 404 页面</h1>;

运行时减小 32+ kB(Gzip 减小 15+ kB)

Next.js 支持 与 React 相同的浏览器,无需额外配置。这包括 Internet Explorer 11 (IE11) 和所有主流浏览器(Edge、Firefox、Chrome、Safari、Opera 等)。

作为此兼容性的一部分,我们还会将您的应用编译为兼容 IE11:这允许您安全地使用 ES6+ 语法特性、Async/Await、对象剩余/扩展属性等——所有这些都无需任何配置。

此编译过程还包括透明地注入必要的功能 polyfill(如 Array.fromSymbol)。然而,这些 polyfill 仅对 不到 10% 的网络流量 是必要的,大多数情况下是为了支持 IE11。

从 Next.js 9.3 开始,Next.js 会自动加载支持旧版浏览器所需的 polyfill,并且仅在这些旧版浏览器中加载 polyfill。

实际上,这意味着对于 90%+ 的用户首次加载 大小将减少 32 kB 或更多。

对于依赖更多浏览器特性的大型应用,这些大小节省甚至更大。

此优化完全自动进行,无需对应用进行任何更改即可受益!

社区

我们非常高兴看到 Next.js 的持续增长:

  • 我们有超过 927 位独立贡献者。
  • 在 GitHub 上,该项目已获得超过 46,600 次 star。
  • 示例目录 包含超过 226 个示例。

Next.js 社区现在拥有超过 15,250 名成员。社区现在可以在 GitHub discussions 上找到,这是一个供社区讨论和提问的新地方!加入我们!

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

特别感谢 Jeff Escalante 对新数据获取方法的重要反馈。

衷心感谢所有为此版本做出贡献的人:@arcanis, @lgordey, @ijjk, @martpie, @jaywink, @fabianishere, @dijs, @TheRusskiy, @quinnturner, @timneutkens, @lfades, @vvo, @adithwip, @rafaelalmeidatk, @bmathews, @Spy-Seth, @EvgeniyKumachev, @chibicode, @piglovesyou, @HaNdTriX, @Timer, @janicklas-ralph, @devknoll, @prateekbh, @ethanryan, @MoOx, @rifaidev, @msweeneydev, @motiko, 和 @balazsorban44 的帮助!