findxc / blog

88 stars 5 forks source link

Next.js 入门 #69

Open findxc opened 2 years ago

findxc commented 2 years ago

一直以为 Next.js 就只是用来做服务端渲染的 React 框架,最近仔细看了下文档,才发现不仅仅是服务端渲染,用来做纯静态网站,比如公司官网、文档网站也是完全 ok 的。

Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed.

Next.js 默认有做 route pre-fetching ,当 Link 出现在视图中时,会去提前请求相关的资源,这样跳转路由界面切换会很快。

PS:咋感觉这种路由跳转的预处理是基操呢,之前在 React 官网为什么这么快 里面提到的 Gatsby 有做类似处理,然后在 通过切割代码和预加载来提高页面加载速度 里面提到的 quicklink 也是用来做路由层面的预加载的。

好吧,不扯远了,在开始学习 Next.js 之前,我们先来弄清楚 SSG、SSR、CSR 分别是什么,这样当你实际开发时,可以针对具体业务场景选择不同方式。

SSG、SSR、CSR 分别是什么

CSR

Client Side Rendering ,客户端渲染。

用户访问某个 URL 时,服务端返回的 HTML 不包含页面具体内容,这种方式就无法考虑 SEO 了。

当 JS 文件下载和执行后,页面才会更新。所以相对于 SSR 和 SSG 来说页面内容展示会慢一些。

SSG

Static Site Generation ,静态站点生成。

打包时就会为每个页面生成包含内容的 HTML 文件,这样用户访问某个 URL 时,服务端直接返回 HTML 即可。

5BD3646E-3E8A-4C05-80CE-04240B580E8F

这种形式页面加载是最快的,也利于 SEO ,前提是页面内容在打包时就能确定,或者说大部分内容能确定,然后小部分内容通过客户端渲染来更新(结合 SSG 和 CSR 的方式)。

SSR

Server Side Rendering ,服务端渲染。

当用户访问某个 URL 时,服务端实时生成包含内容的 HTML 文件再返回,由于需要实时生成 HTML ,对服务器的压力会大一些。页面加载相对 SSG 会慢一些。由于会返回包含内容的 HTML,所以也是利于 SEO 的。

如何选择

Next.js 是建议尽量采用 SSG ,或者 SSG + CSR 的方式,这样页面加载会比较快。在必要时才去使用 SSR 。

然后在 Next.js 中,如果有用到 SSG 或者 SSR ,其实也只有首屏加载时是服务端返回包含内容的 HTML ,后续通过 Link 跳转是客户端跳转,页面内容的更新是通过 JS 实现的,而不是说又去请求相应的 HTML 。

在 Next.js 中怎么实现 SSG 和 SSR

Next.js 默认认为 pages 目录下每个文件对应一个路由,每个文件中默认导出的组件就是页面组件。

SSR

对于 SSR ,页面文件中需要额外导出 getServerSideProps ,这个函数只会在服务端执行。

// 这里的 data 来自于下面 getServerSideProps 的返回值
function Page({ data }) {
  // Render data...
}

// 每次请求这个页面时,这个函数就会在服务端执行
export async function getServerSideProps() {
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // 这里返回的 props 会注入到页面组件中
  return { props: { data } }
}

export default Page

如果是通过 Link 进行的客户端跳转,这个函数也会执行,但是还是在服务端执行的。通过 Link 跳转时, Next.js 会发一个请求让服务端来执行这个函数,详见 When does getServerSideProps run

SSG

如果页面文件没有导出 getServerSideProps ,那么 Next.js 就会认为这个页面是 SSG 。

如果静态页面生成时,需要请求额外数据,比如博客网站可能需要向 CMS - 内容管理系统 请求目录和博客内容,这时页面文件可以导出 getStaticProps 函数来为页面组件注入属性。

// 这里的 posts 来自于下面 getStaticProps 的返回值
function Blog({ posts }) {
  // Render posts...
}

// getStaticProps 在打包时会执行
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()
  // 这里 return 的 props 会注入给页面组件
  return { props: { posts } }
}

export default Blog

如果静态页面生成时,页面对应的路由是希望动态生成的,比如博客网站每篇博客的路由是文件名,可以使用 getStaticPaths 来实现,详见 Scenario 2: Your page paths depend on external data

export async function getStaticPaths() {
  // 发请求获取相关数据,然后构造 paths
  return {
    // paths 就是所有的路由
    paths: [
      { params: { ... } }
    ],
    fallback: true // false or 'blocking'
  };
}

getStaticPaths 返回值中的 fallback 是用来设置当用户请求的路径在 paths 中不存在时期望 Next.js 如何处理。

然后 Next.js 还支持 增量静态生成 - Incremental Static Regeneration 这个功能,通过在 getStaticProps 的返回值中设置 revalidate 来生效。

比如我有个接口 http://localhost:4000/date 可以获取服务器当前时间,然后我写了一个 SSG 的页面来展示这个时间,我设置了 revalidate 为 10 ,也就是 10s 。

function Page(props) {
  const { date } = props
  return <div>服务端当前时间:{date}</div>
}

export async function getStaticProps(context) {
  const res = await fetch('http://localhost:4000/date')
  const { date } = await res.json()
  // 10s 内服务端最多更新一次该页面
  return { props: { date }, revalidate: 10 }
}

export default Page

那当我请求这个页面时, Next.js 会先返回已经生成的 HTML ,同时会去生成新的 HTML ,这样下次再请求就能拿到这个新的 HTML 。服务端的更新频率就是由 revalidate 来设置的。注意,只有在生产环境下才是这样的,在开发环境每次请求页面都会生成新的 HTML 。

8AE2ECA0-1DCB-4BAA-B5CD-A803DABDBD7D

增量静态生成这个功能适用于静态页面可能会更新的场景,就不用重新打包和部署了,只是说用户可能会访问到旧的 HTML 。

再补充一下, Next.js 打包后显示的信息会告诉你哪些路由是服务端渲染哪些是静态生成的,比如下图中,路径前有 λ 的表示是服务端渲染,有 ○ 表示是静态生成。

6B597AA4-173F-402F-9093-B98EFF7C2840

在 Next.js 中怎么加上 layout

可以使用 custom app 的方式来为页面统一加上 layout ,详见 Layouts

// pages/_app.js
import { useRouter } from 'next/router'

function MyApp({ Component, pageProps }) {
  const router = useRouter()
  console.log(router.pathname)

  return (
    // 使用同一个 Layout 或者根据不同 pathname 使用不同 Layout
    // <Layout>
    <Component {...pageProps} />
    // </Layout>
  )
}

export default MyApp

在 Next.js 中怎么代理 API 请求

可以使用 Rewrites 功能来代理 API 请求。

module.exports = {
  async rewrites() {
    return [
      {
        // 把 /api 的请求都代理到 http://localhost:4000 上去
        source: '/api/:path*',
        destination: 'http://localhost:4000/:path*',
      },
    ]
  },
}

在 Next.js 中怎么设置环境变量

比如希望把后端请求地址配置在环境变量中,增加 .env.development.env.production 文件即可,分别对应开发环境和生产环境的环境变量,在 next.config.js 或者 getStaticProps 等中通过 process.env.xxx 即可读取。更多配置见 Basic Features: Environment Variables | Next.js

Next.js 默认支持 styled-jsx

详见 CSS-in-JS

function HelloWorld() {
  return (
    <div>
      Hello world
      <p>scoped!</p>
      <style jsx>{`
        p {
          color: blue;
        }
      `}</style>
    </div>
  )
}

export default HelloWorld

总结

好吧,上面就是最近学习 Next.js 总结的一些比较常用的功能,通过看文档对它有了一个更全面的认识。

RealTong commented 2 years ago

做文档网站的话, 推荐一下 Nextra

findxc commented 2 years ago

@kardenwu 可以, demo 的样式很简洁清爽