跳至内容
返回博客

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

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

getStaticProps 始终在 Node.js 上下文中运行,并且代码会自动从浏览器捆绑包中进行树状抖动,以确保发送到浏览器的代码更少。这样,您就不必担心在 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。当某个路径在构建时未生成时,将在用户请求页面时按需生成。

当你的应用程序有很多可以静态生成的路由,但你不想因为只在构建时生成一部分页面而增加构建时间时,这将非常有用。

触发页面生成的用户的浏览器会得到一个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始终在服务器端运行,并且代码会自动从浏览器捆绑包中剔除,从而确保发送到浏览器的代码更少。这样,你就不必担心在服务器和浏览器环境中执行数据获取代码,因为它们之间存在一些不一致性。在许多情况下,这会提高性能,因为服务器通常与数据源具有更快的连接。它还通过减少数据获取逻辑的暴露来提高安全性。

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

当使用next/link在页面之间导航时,而不是在浏览器中执行getServerSideProps,Next.js会向服务器发出一个请求,该请求将返回调用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。相比之下,使用原生 CSS 的占 44%,使用 Less 的占 6%。

此外,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 模块支持组件级样式

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文件,它仍然会在构建时自动进行静态优化。如果您的应用程序包含 404 页面,则此页面将用于代替pages/_error.js来渲染 404。

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

32+ kB 更小的运行时 (15 kB+ Gzip)

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次。
  • 示例目录拥有超过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 的帮助!