跳到内容
返回博客

2020年3月9日,星期一

Next.js 9.3

发布者

我们很高兴今天推出 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**)或网站的博客部分提供支持的营销页面。

我们与 HashiCorp 等 SSG 和 next export 的重度用户合作,并在 Next.js 历史上评论最多的 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 并为 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 }) {
  // 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 不会在浏览器中执行 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 边缘网络无缝实现。

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

终端
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 Module 支持

Next.js 现在支持使用 [name].module.scss 文件命名约定,将CSS Modules与 Sass 文件一起使用。

与 Next.js 5+ 中使用 next-sass 提供的支持不同,全局 Sass 和 CSS 模块现在可以**共存**—— next-sass 要求应用程序中的所有 .scss 文件都被视为全局或本地,但不能两者兼而有之。

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

这种行为使 CSS Modules 成为包含组件级别 Sass 的理想方式。CSS Module 文件**可以在应用程序中的任何位置导入**。

要开始在您的应用程序中使用 Sass CSS Modules,请确保您已安装 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 Modules 是一个*可选*功能,并且只对具有 .module.scss 扩展名的文件启用。常规的<link> 样式表全局 Sass 样式仍然受支持。

在生产环境中,所有 CSS Module 文件会自动合并到**多个经过压缩和代码分割的 .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,它仍将在构建时自动静态优化。如果您的应用程序有自定义的 404 页面,则此页面将代替 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、Object Rest/Spread Properties 等——所有这些都无需配置。

此编译过程的一部分还涉及透明地注入必要的 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 的帮助!