跳到内容
返回博客

2020 年 3 月 9 日,星期一

Next.js 9.3

发布者

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

所有这些优点都是非破坏性的,并且完全向后兼容。您只需运行以下命令即可更新:

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

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

在构建网站或 Web 应用程序时,您通常需要在 2 种策略之间进行选择:静态生成 (SSG) 或服务器端渲染 (SSR)。

Next.js 是第一个混合框架,允许您在每个页面的基础上选择最适合您用例的技术。

Next.js 9.0 引入了 自动静态优化 的概念。当页面没有像 getInitialProps 这样的阻塞数据获取要求时,它将在构建时自动渲染为 HTML。

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

我们与 SSG 和 next export 的重度用户(如 HashiCorp)进行了合作,并在 评论最多的 RFC 中与社区广泛讨论了正确的约束,以创建一种新的统一方式来进行数据获取和静态生成。

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

getInitialProps 模型相比,这些新方法具有许多优势,因为 SSG 与 SSR 之间有明显的区别。

  • getStaticProps(静态生成):在构建时获取数据。

  • getStaticPaths(静态生成):指定要基于数据预渲染的动态路由

  • getServerSideProps(服务器端渲染):在每次请求时获取数据。

  • 这些改进是 API 的新增功能。所有新功能都完全向后兼容,并且可以逐步采用。没有引入弃用,getInitialProps 将继续按当前方式运行。我们鼓励在新页面和项目上采用这些新方法。

getStaticProps

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

getStaticProps 始终在 Node.js 上下文中运行,并且代码会自动从浏览器包中进行 tree-shaking,从而确保发送到浏览器的代码更少。这样,您不必担心数据获取代码在 Node.js 和浏览器环境中的执行,这两者之间存在一些不一致之处。

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

pages/posts/[id].js
export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

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

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

这是一个使用 getStaticProps 从 CMS 获取博客文章列表的示例:

pages/blog.js
// You can use any data fetching library
import fetch from 'node-fetch';
 
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}
 
// This function gets called at build time in the Node.js environment.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // By returning { props: posts }, the Blog component
  // will receive `posts` as a prop at build time
  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: { ... } } // See the "paths" section below
    ],
    fallback: true or false // See the "fallback" section below
  };
}

paths 键(必需)

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

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
  • 如果页面名称使用 catch-all 路由,例如 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 }) {
  // Render post...
}
 
// This function gets called at build time
export async function getStaticPaths() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts');
  const posts = await res.json();
 
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => `/posts/${post.id}`);
 
  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false };
}
 
// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Pass post data to the page via 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();
 
  // If the page is not yet generated, this will be displayed
  // initially until getStaticProps() finishes running
  if (router.isFallback) {
    return <div>Loading...</div>;
  }
 
  // Render post...
}
 
// This function gets called at build time
export async function getStaticPaths() {
  return {
    // Only `/posts/1` and `/posts/2` are generated at build time
    paths: [{ params: { id: 1 } }, { params: { id: 2 } }],
    // Enable statically generating additional pages
    // For example: `/posts/3`
    fallback: true,
  };
}
 
// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();
 
  // Pass post data to the page via props
  return { props: { post } };
}
 
export default Post;

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

getServerSideProps

如果您从页面导出名为 getServerSidePropsasync 函数,Next.js 将在每次请求时渲染此页面 (SSR)。

getServerSideProps 始终在服务器端运行,并且代码会自动从浏览器包中进行 tree-shaking,从而确保发送到浏览器的代码更少。这样,您不必担心数据获取代码在服务器和浏览器环境中的执行,这两者之间存在一些不一致之处。在许多情况下,这会提高性能,因为服务器通常与数据源的连接速度更快。它还通过减少数据获取逻辑的暴露来提高安全性。

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

当使用 next/link 在页面之间导航时,Next.js 将执行 fetch 到服务器,而不是在浏览器中执行 getServerSideProps,服务器将返回调用 getServerSideProps 的结果。

pages/index.js
export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

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

  • params:如果此页面使用动态路由,则 params 包含路由参数。如果页面名称为 [id].js,则 params 将类似于 { id: ... }。要了解更多信息,请查看动态路由文档
  • reqHTTP 请求对象
  • resHTTP 响应对象
  • query:查询字符串。

这是一个使用 getServerSideProps 在请求时获取数据并进行渲染的示例:

pages/index.js
function Page({ data }) {
  // Render data...
}
 
// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`);
  const data = await res.json();
 
  // Pass data to the page via props
  return { props: { data } };
}
 
export default Page;

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

预览模式

正如本文前面讨论的那样,当您的页面从无头 CMS 获取数据时,静态生成非常有用。但是,当您在无头 CMS 上编写草稿并希望立即在页面上预览草稿时,它并不理想。由于输出是静态的,因此预览更改变得更加困难,因为您必须重新生成该静态页面。

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

例如,当从无头 CMS 预览草稿时,您希望绕过静态渲染,并按需渲染带有草稿内容的页面,而不是已发布的内容。您希望 Next.js 仅针对此特定情况绕过静态生成。

我们很高兴地宣布 Next.js 的一项新的内置功能来解决此需求:预览模式。

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

但是,您不仅仅限于某些 CMS 系统。预览模式直接与 getStaticPropsgetServerSideProps 集成,因此可以与任何类型的数据获取解决方案一起使用。

使用 next start 或通过部署Vercel Edge 网络 时,预览模式已可用。

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-sassnext-css 具有相同的缺失约束。这意味着您可以在项目的每个文件中导入 Sass 文件,但是,此导入的 Sass 文件将全局应用于整个应用程序。

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

要开始在您的应用程序中使用全局 Sass 导入,请安装 sass

终端
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';
 
// This default export is required in a new `pages/_app.js` file.
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 文件命名约定将 CSS Modules 与 Sass 文件一起使用。

与之前在 Next.js 5+ 中使用 next-sass 提供的支持不同,全局 Sass 和 CSS 模块现在可以共存 —— next-sass 要求您应用程序中的所有 .scss 文件都必须作为全局或局部文件处理,而不能两者兼得。

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

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

要开始在您的应用程序中使用 Sass CSS 模块,请确保您已安装 sass

终端
npm install sass

现在,考虑一个在 components/ 文件夹中的可重用 Button 组件

首先,创建 components/Button.module.scss,内容如下

/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
$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"
      // Note how the "error" class is accessed as a property on the imported
      // `styles` object.
      className={styles.error}
    >
      Destroy
    </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,它将继续用于 404 页面,直到您添加 pages/404.js

默认静态 404 页面

当您的应用程序没有自定义的 pages/_error.js 页面时,Next.js 将自动静态生成 404 页面,并在需要提供 404 错误时使用该页面。这是自动发生的,无需进行任何更改。

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

要覆盖默认的 404 页面,您现在可以创建一个 pages/404.js,它仍然会在构建时自动进行静态优化。如果您的应用程序有 pages/404.js,则将使用此页面代替 pages/_error.js 来渲染 404 错误。

pages/404.js
export default () => <h1>This is the 404 page</h1>;

运行时体积减少 32+ kB (Gzip 压缩后减少 15 kB+)

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

作为此兼容性的一部分,我们还将您的应用程序编译为与 IE11 兼容:这使您可以安全地使用 ES6+ 语法特性、Async/Await、对象 Rest/Spread 属性等等 —— 所有这些都无需任何配置。

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

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

在实践中,这意味着对于 90% 以上的用户 来说,您的首次加载大小将减少 32 kB 或更多。

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

此优化是完全自动的,无需进行任何应用程序更改即可利用它!

社区

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

  • 我们已经有超过 927 位独立贡献者。
  • 在 GitHub 上,该项目已被加星超过 46,600 次。
  • 示例目录 拥有超过 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,感谢你们的帮助!