跳到内容
返回博客

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) 或站点的博客部分驱动的营销页面。

我们与 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-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
  • 如果页面名称使用 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。当某个路径在构建时未生成时,它将在用户请求页面时按需生成。

当您的应用程序有许多可以静态生成的路由,但您不想仅通过在构建时生成子集来增加页面的构建时间时,这非常有用。

触发页面生成的用户将获得一个 fallback 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 Network 时,预览模式已经可用。

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 文件命名约定的 Sass 文件的 CSS 模块

与之前在 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 上,该项目已被 star 超过 46,600 次。
  • 示例目录 拥有超过 226 个示例。

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

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

特别感谢 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!