【笔记系列】Next.js 学习笔记

1,666 阅读9分钟

Next.js 学习笔记

ref: Create a Next.js App

Code splitting 代码拆分

能够自动进行代码拆分,仅在需要加载时加载对应的页面的代码。即首页加载时,其他页面还没初始化呢。

即使页面总量很大,也能保证首页的快速加载。这意味着页面之间是独立的,因此某些报错的页面不会影响正常页面的渲染。

prefetching 预加载

在生产环境构建 production build 的 Next.js,只要包含有 Link 组件出现在视口时,Next.js 会在后台自动预先加载(prefetch)该页面中的内容。当用户点击时,目标页面已经加载完成了,因此页面能瞬间打开。

总结

Next.js 能够自动地优化应用,使用了诸如 代码拆分(Code splitting)、客户端路由(Client-side navigation)以及预加载(prefetching,在生产环境中)。

关于 Link

  • Link 只是一个语义化空壳,里面还是要加上一个 <a> 把内容包起来的。
  • Link 只适用于组件间的路由转换,如果是外部链接,直接用 <a> 即可。
  • Link 上不能够加上属性(如 className、style 等),要加就直接加在里面的 <a> 标签上。

关于 CSS/SASS

  • Next.js 自带对 SASS 的支持,直接 import 就可以用了。
  • 在 JSX 中直接写入 CSS:styled-jsx,能够让我们直接在 js 文件中的 React 组件中写 CSS,样式的将会是 scoped 的,即不会影响到别的组件。
  • Next.js 中自带对 styled-jsx 的支持,能够直接使用。此外也能够使用其他 CSS-in-JS 的库,如:styled-components 或者 emotion
<style jsx>{`
  main {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
`}</style>

关于静态资源

静态资源(如图片、robots.txt、谷歌网站验证文件)都被放在了 top-level 目录 /public 里了。在 pages 的 js 页面中引用 public 目录中的资源可以直接像在根目录中引用一样。

<img src="/vercel.svg" alt="Vercel Logo" className="logo" />

元数据

由于 JSX 本质上还是 JS,所以在处理元数据时需要不能直接用 createElement 方法(即 JSX)生成。因此需要引入元数据 React 组件。

next/head 引入的 <Head> 是一个 Next.js 的 React 组件,用来构建页面的头部。里面能够定义页面的 <title> 了。

Layout 组件

  • 接收一个 { 对象 } 作为参数,返回一个包起这个对象的 React 组件。类似于 Wrapper 的一个东西。实际上作用就是一个负责布局、添加样式的东西。
  • 在使用时只需要将该 Layout 引入,然后再把这个 Layout 包起想要添加布局的组件即可。

CSS Module (.module.css)

实际上就是一个 CSS 文件,只不过后缀从 .css 变成了 .module.css

  • 使用的时候把 .module.css 文件直接当作一个模块引入到 js 文件中,并把它赋给一个叫 styles 的样式对象。
  • 这个 styles 实际上就是一个样式模块,在 JSX 中使用的时候,直接在属性那里写 className = { styles.<在样式模块中对应的样式类名> }。但是实际上并不需要我们给类起名,CSS Module 会自动给该 JSX 组件生成一个独一无二的类名,并应用上我们指定的 样式模块 中的样式。
  • 它的目的是让我们能够为每个页面加载最少的 CSS 文件,从而缩小 包 bundle 的大小。
  • Next.js 会在构建时自动提取 JS 包里的 CSS Modules ,并且自动生成对应的 .css 文件。

全局样式 (_app.js)

  • 全局样式放在了 pages/_app.js 中,我们在里面定义一个返回值是一个 <Component> (Component 为对应输入的组件对象)的函数 App,它将会是一个 top-level 的组件,在不同的组件中都会使用到它,即使在不同页面中跳转,我们也可以用它来保存 state。
  • 全局样式 global CSS 位于 top-level 的 styles/global.css,在 pages/_app.js 中 import 该样式文件即可生效(修改了 _app.js 必须 restart 应用)。
  • 因为修改全局样式会影响所有的元素,因此在 pages/_app.js 之外不能够引入 global.css
export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

classnames 用于增删 class

classnames 是一个简单的库,能够让我们增删 class。 用法:

import styles from "./alert.module.css";
import cn from "classnames";

export default function Alert({ children, type }) {
  return (
    <div
      className={cn({
        [styles.success]: type === "success",
        [styles.error]: type === "error",
      })}
    >
      {children}
    </div>
  );
}

alert.module.css

.success {
  color: green;
}
.error {
  color: red;
}

PostCSS

默认 Next.js 使用了 PostCSS 进行 CSS 的编译。需要配置 PostCSS 可以在 top-level 新建一个 postcss.config.js,这对使用如 Tailwind CSS 很有用。

安装:

npm install tailwindcss postcss-preset-env postcss-flexbugs-fixes

配置 postcss.config.js:

module.exports = {
  plugins: [
    "tailwindcss",
    "postcss-flexbugs-fixes",
    [
      "postcss-preset-env",
      {
        autoprefixer: {
          flexbox: "no-2009",
        },
        stage: 3,
        features: {
          "custom-properties": false,
        },
      },
    ],
  ],
};

配置 tailwind.config.js:

// tailwind.config.js
module.exports = {
  purge: [
    // Use *.tsx if using TypeScript
    "./pages/**/*.js",
    "./components/**/*.js",
  ],
  // ...
};

使用了 tailwind.js 可以,移除没用到的 css 文件。

使用 Sass

因为默认就配置好了,直接引入 Sass 都是可以的, 包括 .sass 和 .scss 两种拓展名的文件都能直接引入。可以使用组件级的 Sass,即以 .module.scss.module.sass 结尾的。

事前需要先安装好 sass: npm install sass

预渲染 Pre-rendering

Next.js 最重要的概念之一,默认会对每个页面都进行预渲染(Pre-rendering)。

  • 这个在服务端进行的,即 SSR(Server-Side Rendering)。
  • Next.js 会提前对每个页面生成一份 HTML 代码,而不是在客户端拿到 raw HTML 让浏览器去解析、渲染整个页面。
  • 预渲染能够:提高性能 + 优化 SEO
  • 每个生成的 HTML 页面都只包含必须的 JS 代码。当页面被加载完成后,其 JS 代码才会开始运行并使得页面完全可交互的,这个过程叫做 水合(Hydration)。

禁用 JS

如果我们 disable javascript(F12,ctrl+shift+p, "disable javasctipt"),将会看到页面可以在没有 JS 的情况下渲染。这是因为 Next.js 进行了预渲染得出的 HTML。

如果对一个纯粹的 React 页面(没使用 Next.js),我们将什么也看不见(因为 React 本质就是 JS 写 HTML,没 JS 写不了的)。

两种预渲染

  1. 静态生成 Static Generation:仅在构建时生成 HTML,以后每次请求都复用这个生成的预渲染 HTML。
  2. 服务端渲染 SSR:对每次请求都用预渲染方法生成一个 HTML。

在开发模式 npm run dev 下,每次都会对每个请求把所有页面预渲染,哪怕你选的是静态生成。

Next.js 能够让我们自己选择每个页面是通过哪种预渲染方式。我们能创建混合 App,即对大部分页面使用 静态生成,对小部分页面使用 服务器渲染。

什么时候使用

推荐用 静态生成 Static Generation,因为可以只构建一次,然后放在 CDN 上去让用户访问,能够比 SSR 每次都通过服务器响应请求并进行一次预渲染更快。

静态生成 Static Generation 适用于一切能够在用户请求之前就把页面整好的情况,如:

  • 电商
  • 博客
  • 主页
  • 帮助、文档

如果页面内容和每次请求都相关,显示更新数据等,应该考虑 SSR,每次都让服务器预渲染一次,虽然慢些,但能使页面保持最新。

使用 getStaticProps 来静态生成

每当我们 export 一个组件时,我们可以同时 export 一个 async 的函数getStaticProps

  • 它能够在生产环境下(production)构建项目时运行,函数内我们可以进行一些外部数据获取,并把外部数据自动返回给页面组件作为参数。
  • 在开发环境下(development),该函数在组件遇到每一个请求时都会运行一遍。
  • 如果需要在每次请求时都运行一次数据获取,应该使用 服务器端渲染 SSR
  • getStaticProps 只能够在一个页面 page 中存在并 export 出去(如 pages/index.js),因为 React 有个限制,就是它需要等到把所有数据都加载完了才能够渲染这个页面。
export default function Home(props) { ... }

export async function getStaticProps() {
  // Get external data from the file system, API, DB, etc.
  const data = ...

  // The value of the `props` key will be
  //  passed to the `Home` component
  return {
    props: ...
  }
}

getStaticProps 获取的数据来源多种多样

  • 从 file system 读取
  • fetch 获取外部资源(Next.js 自带实现,不用 import 了)
  • 从后端数据库获取(因为 getStaticProps 仅在服务器端运行,因此不用担心它被发送到浏览器执行)

使用 getServerSideProps 来 SSR

当我们需要每次请求都进行一次数据请求(fetch),我们应该使用的就是 SSR,这时请求数据的方法不是 getStaticProps 而是 getServerSideProps 了。

export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    },
  };
}

相对于 getStaticPropsgetServerSideProps 方法多了一个 context 参数,它包含了当前请求的特定参数。

通常情况下,如果不是获取的数据和请求息息相关的,不建议使用 getServerSideProps。它的首个比特时间(TTFB,Time to first byte)会比 getStaticProps 更慢,是因为每次它都需要服务器进行计算预渲染的结果,并且它在没配置的情况下是不能够被 CDN 缓存的。

数据的客户端渲染 CSR

注意不是页面的客户端渲染。

  • 将页面不需要动态数据的部分 静态生成(预渲染)
  • 当页面加载时,使用 JS 获取外部数据并补充页面剩余的部分
  • 可用于用户 dashboard 页,私人的页面、不需要 SEO、不需要预渲染、经常需要更新

SWR 钩子

如果时在客户端获取数据,使用 Next.js 制作的 swr 钩子能够很好地实现数据 fetch。它能够缓存、重新认证、焦点捕捉、定间隔重新获取等。

全称 stale-while-revalidate,详见官方文档

import useSWR from "swr";

function Profile() {
  const { data, error } = useSWR("/api/user", fetch);

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return <div>hello {data.name}!</div>;
}

动态路由

可创建静态路由如 pages/posts/[id].js,所有用左右中括号括起来的在 Next.js 中都是动态路由。

使用 getStaticPaths 来获取所有可能的博客路径,使用 getStaticProps 来获取一个给定 id 的特定文章。

动态路由匹配多个路径 catch-all routes

pages/posts/[...id].js 能匹配 /posts/a/posts/a/b/posts/a/b/c 等等。

getStaticPaths 中的 id 就需要返回一个数组了,因为这种多层级的路由是以数组形式设定的。

return [
  {
    params: {
      // 能够生成 /posts/a/b/c
      id: ["a", "b", "c"],
    },
  },
];

fallback

getStaticPaths 的返回值有一个参数 fallback: false,意思是当你遇到 getAllPaths 的路径列表之外的页面的时候的处理是怎么样的。

  1. fallback: false 即所有不在 getStaticPaths 返回值里的路径都会返回 404 Page
  2. fallback: true, 那么 getStaticProps 的行为就会被改变了:1)所有的 getAllPaths 的路径列表里的页面会在构建时渲染成 HTML 2)所有路径列表之外的路径不会导致 404 Page,而是在对此路径的第一个请求时提供页面的“后备”(fallback)版本 3)后台,Next.js 将静态生成请求的路径,后续请求都会对应该生成的后备页面。
  3. fallback: blocking,即新的路径会被 SSR 且使用了 getStaticProps 里面的参数,这个结果会被缓存,因此该路径只会被请求一次。

路由

直接使用 Next.js 路由,应该 import 一个 useRouter