next.js pages页面预渲染

22 min read

概念

在nex.js 中,page指的就是在pages下的React组件

它同时关联着路由

预渲染

预渲染更有利于SEO,nex.js默认的会预渲染所有的页面来替代传统的SPA页面

两种预渲染形式

  • 静态生成(Static Generation ):静态生成的页面是在构建生成,每次请求时重用
  • 服务端渲染(Server-side Rendering):静态页面在每次请求时生成

选哪种形式?

next.js 可以让开发者有选择的使用这两种形式的渲染方式,开发者也可以选择混合类型的渲染形式,也就是一部分页面使用静态生成,一部分使用服务端渲染

nex.js官网推荐使用静态生成,生成的页面可以托管在CDN,加速访问

静态生成的两种形式

  • 无数据渲染: 没有接口交互,直接生成按代码生成相关的HTML静态页面
  • 有数据渲染: 有数据接口交互 ,根据接口生成对应的HTML静态页面

最简单,同时性能也最优的预渲染方式就是静态生成(SSG),把组件渲染工作完全前移到编译时:

  1. (编译时)获取数据
  2. (编译时)渲染组件,生成 HTML

将生成的 HTML 静态资源托管到 Web 服务器或 CDN 即可,兼具 React 工程优势与 Web 极致性能

那么首先要解决如何获取数据的问题,Next.js 的做法是将页面依赖的数据集中管理起来:

// pages/index.js
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只在服务端执行(根本不会进入客户端 bundle),返回的静态数据会传递给页面组件(上例中的Home)。也就是说,要求通过getStaticProps提前备好页面所依赖的全部数据,数据 ready 之后组件才开始渲染,并生成 HTML

P.S.注意,只有页面能通过getStaticProps声明其数据依赖,普通组件不允许,所以要求将整页依赖的所有数据都组织到一处

至于渲染生成 HTML 的部分,借助React 提供的 SSR API即可完成

至此,只要是依赖数据有办法提前获取到的页面,理论上都可以编译生成静态 HTML,但 2 个问题也随之而来:

  • 数据可能会发生变化,已经生成的静态页面需要更新
  • 数据量可能会多到“永远”编译不完

以电商页面为例,要把海量商品数据全都编译成静态页面,几乎是不可能的(或许要编译一个世纪那么长),即便都生成了,商品信息也会时不时地更新,静态页面需要重新生成:

If your app has a very large number of static pages that depend on data (think: a very large e-commerce site). You want to pre-render all product pages, but then your builds would take forever.

因此,增量静态再生成(Incremental Static Regeneration)应运而生

ISR 支持

对于编译时无法穷举的海量页面以及需要更新的场景,Next.js 允许运行时再生成(相当于运行时静态化):

Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in.

例如:

export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // 设置有效期,开启ISR
    revalidate: 1, // In seconds
  }
}

revalidate: 1表示运行时(用户请求打过来时)尝试重新生成静态 HTML,1秒最多重新生成一次

运行时静态生成需要一些时间(用户请求等着要 HTML),在此过程中有 3 种选择:

  • fallback: false:不降级,命中尚未生成静态页面的路由直接 404
  • fallback: true:降级,命中尚未生成静态页面的路由先返回降级页面(此时props为空,一般显示个 loading),静态生成 HTML 的同时会生成一份 JSON 供降级页面 CSR 使用,完成之后浏览器拿到数据(在客户端填上props),渲染出完整页面
  • fallback: 'blocking':不降级,并且要求用户请求一直等到新页面静态生成结束(实际上就是 SSR,渲染过程是阻塞的,只是完成之后会保留结果 HTML)

即结合路由(getStaticPaths)对尚未生成的页面进行降级,例如:

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

function Post({ post }) {
  const router = useRouter()

  // 渲染降级页面
  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render post...
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    // (页面级)降级策略,true表示遇到尚未生成的先给个降级页,生成完毕后客户端自动更新过来
    fallback: true,
  }
}

P.S.具体见Incremental Static Regeneration、以及The fallback key

然而,并非所有场景都能愉快地在编译时静态生成。典型的,如果组件依赖的数据是动态的,显然无法在编译时预先取得数据,静态生成就无从谈起了

五.SSR 支持

对于编译时无法生成静态页面的场景,就不得不考虑 SSR 了:

区别于 SSG getStaticProps,Next.js 提供了 SSR 专用的getServerSideProps(context)

// pages/index.js
export async function getServerSideProps(context) {
  const res = await fetch(`https://...`)
  const data = await res.json()

  if (!data) {
    return {
      notFound: true,
    }
  }

  return {
    props: {}, // will be passed to the page component as props
  }
}

同样用来获取数据,与getStaticProps最大的区别在于每个请求过来时都执行,所以能够拿到请求上下文参数(context