React笔记五:使用Next.js实现React SSR的优雅降级

前言

React SSR最成熟的开源框架是Next.js,这么多年保持着强劲的生命力,它的创始团队vercel(曾用名zeit),如今更关注于SSR和serverless的结合。随着服务端的容器化技术以及serverless技术不断完善,在国外可能SSR的降级已经不是一个必要命题。但是,考虑到国内的服务环境,今天我们还是有必要从前端的技术点讨论一下如何去实现SSR的优雅降级。

旧版本的Next.js是利用getInitProps实现服务端渲染以及静态站点生成。在Next.js 9.3版本后,getInitProps这个api被替换成为三个不同的api,分别是:

  • getStaticProps (静态页面生成SSG): 构建的时候生成页面
  • getStaticPaths (静态页面生成SSG): 根据构建内容去生成动态路由
  • getServerSideProps (服务端渲染SSR): 在每个请求中在服务端获取数据渲染页面

这三个api的使用是对一个项目中不同页面的更细程度的划分,它可以有效区分哪些页面走SSR、哪些页面走CSR和SSG。高效的划分了这三种不同的渲染模式。

What is JAMstack ?

静态页面生成SSG这种模式更加符合JAMstack的标准,所有的页面都是提前预渲染的,静态的页面可以直接托管在CDN上,有效降低运维成本,有助于你“高效下班”。Next.js官方建议你优先使用静态页面生成,不得已才使用服务端渲染。但是静态页面不能满足你的所有case。只有以下情况才比较适合静态页面生成:

  • 数据能通过CMS接口有效渲染
  • 数据能够公开缓存,并且不能用户特有的
  • 页面必须预渲染,并且SEO敏感

Next.js已经能够在一个项目不同路由支持不同的渲染模式。

源码参考 brandonxiang/example-nextjs ,页面的逻辑放在modules文件夹里面,用一个自定义的函数getPrerenderProps来保证页面的预渲染逻辑。这个预渲染逻辑如下,即获取数据传递到组件当中与Next.js的预渲染api类似。

// modules/Home.tsx
export const getPrerenderProps =  async (ctx) => {
  // SSG读取环境变量,并作为兜底参数
  const defaultLimits =  process.env.limits || 0;
  // SSR和CSR动态渲染从URL上获取参数
  const _limits =  (ctx?.query?._limits) || defaultLimits;
  // 获取远程动态数据
  const res = await axios.get(
   'https://jsonplaceholder.typicode.com/photos?_limit=' + _limits
  )
  // 传递给各种渲染模式
  return { props: { photos: res.data } }
}

自定义页面渲染函数Page来保证页面dom的渲染,这里的目标是“一份核心代码,多种渲染模式”。数据photos则会在页面中渲染。

// modules/Home.tsx
function Home({ photos }) {
  let _photos =  photos || []
  return (
    <div className="photos">
      {
        _photos.map((photo, index) => (
          <figure key={index}>
            <img src={photo.thumbnailUrl} alt={photo.title} />
            <figcaption>{photo.title}</figcaption>
          </figure>
        ))
      }
    </div>
  )
}

export const Page = Home;

然后将它渲染到三种不同的模式当中。由于 Next.js 的文件路由设定,页面需要被设置成为三种:

  • index.js SSR模式
  • index_ssg.js SSG模式
  • index_csr.js CSR模式

Next.js如何实现SSR

SSR模式需要将自定义的getPrerenderProps 输出到页面级别Next.js API的getServerSideProps当中,获取数据的逻辑将会提前在服务端完成。此时,服务端可以实现页面的动态渲染。Page则返回给整个页面的渲染函数。

// index.js
export { 
  Page as default, 
  getPrerenderProps as getServerSideProps 
} from '../modules/Home';

Next.js如何实现CSR

CSR模式则是自定义的getPrerenderProps 在useEffect中渲染,在页面加载之后,重新对页面进行渲染,达到一个客户端渲染的效果。路由参数发生变化,页面会重新进行渲染,保证的页面的动态可用。这种模式页面的渲染会比较慢,时长主要是请求时长。

// index_csr.js
export default () => {
    const router = useRouter();
    const [extraProps, setExtraProps] = useState({});

    useEffect(() => {
        getPrerenderProps(router).then(({props}) => {
            setExtraProps(props);
        }) 
    }, [router]);

    return <Page {...extraProps}/>
}

Next.js如何实现SSG

SSG则是静态预渲染,参数不能动态从路由传入,只能构建的时候以环境变量的形式传入,所以页面渲染需要采用特殊的兼容读取方式。

将自定义的getPrerenderProps 输出到页面级别Next.js API的getStaticProps当中,实现静态渲染。

// index_ssg.js
export { 
  Page as default, 
  getPrerenderProps as getStaticProps 
} from '../modules/Home';

如何将SSR降级成为CSR

SSR服务端渲染由于是依赖服务器资源,在流量过大的情况下,有可能会出现服务不可用的情况,返回特殊的错误码例如500等。这时候我们可以实现优雅降级,利用 nginx 做对应的流量分发,当SSR页面返回异常错误的时候,nginx会将流量导入到CSR页面当中。

SSR页面和CSR页面基于Next.js采用同样的业务逻辑编写方式,有效保证页面逻辑的一致性,一份代码两端复用。

SSR优雅的降级

总结

Next.js是非常成熟高效的服务端渲染框架,本文通过一些取巧的方式来实现“一份代码,多种渲染方式”,既能提高页面的性能,也能够保证页面的优雅降级。多种渲染模式采用同一份代码,保证了逻辑的一致性,有效地为QA节省了回归人力。在“质量”和“性能”上找到了一个很好的平衡点。

推荐阅读更多精彩内容