浅窥 nextjs 到目前 v12 版本的几个重点新概念,我们有:
定义 | 说明 |
---|---|
ISR | 增量静态渲染 |
Edge Runtime | 边缘运行时 |
Streaming SSR | 流式传输 SSR |
React Server Components | 服务器组件 |
下面我们对这几个新概念进行一个初步的认知,在阅读前,我们默认读者已经预备了 nextjs 的基本知识。
SSR
、SSG
这两个是冷饭,我们都耳熟能详,那所谓 ISR
增量再渲染的概念,其实是基于 SSG
场景下的混合版 SSR
,也可以理解成加强版 SSG
。
对于 SSG
来说,也要分纯 SSG
和带服务端的 SSG
。
纯 SSG
( next export
)就是构建时预渲染 html
导出纯静态文件的做法,在 v 圈 nuxt 2 中已经被广泛应用。
而考虑有无限个页面(如电商商品等)的网站,还想要享受 SSG
该怎么办?所以我们就引入了服务端,通过服务端来响应访问,按需 SSG
生成新的页面并缓存,来做到无限页面的应对。这个概念在 nextjs 中即 gSP
( getStaticProps
) > fallback
, 在非 fallback: false
的场景下发挥作用。
我们基于第二种带服务端的 SSG
进一步考虑,因为 SSG
一旦执行便产生缓存,此时的问题就变成了我想让我的缓存失效,不然每次都是最旧的 SSG
首页数据多尴尬。
这就产生了 增量 的概念,那分支就又出现了,是被动还是主动让缓存失效?
被动:已有功能,开箱即用,即指 gSP
> revalidate
。
主动:实验性功能,详见 On-demand Revalidation (Beta) ,通过访问 API Routes 来使指定 path 的页面缓存失效(比如商品信息修改,需要让该商品页的缓存马上失效)。
通过以上考虑,我们也逐渐发现 ISR
类似一个带 SSR
要素的 SSG
。
nextjs 在 Self-hosting ISR 中也特别提醒到我们在 k8s 多 pod 的场景下,每个实例都有自己的文件系统来缓存 SSG
的页面,这可能产生某些实例一直未被分配流量,导致 fallback blocking 太久体验较差的问题,所以可能需要共享挂载一个文件系统或分区来解决。
这里就引出了 Vercel 基建的关联意义性:Vercel Incremental Static Regeneration
在 Vercel 内会对 ISR
有更好的全局缓存支持,有兴趣的读者可自行探究,此处不做进一步展开。
综上来看,ISR
的普适性和私有化是可行的,因为在 Vercel 强关联的部分上没有致命要素,只存在性能、体验、成本等方面的浪费。
此处描述的边缘运行时是将 nextjs Middleware 运行在更小的 runtime 中的一种行为,从而进行更快的响应,详见:
该文中对比了现在最流行的三种提供者:nodejs、lambda(如 AWS)、edge 的优缺点,Middleware 目前是 Beta 实验性功能,但他的设定让 nextjs 更有全栈的拓展面,比如 Auth 的前置校验,此处我们也不做更多描述,请在 RFC 中了解更多。
在 RSC
( React Server Components
) 中,我们需要明确指定 edge runtime 来强制 react 18 streaming render,否则会被自动静态优化,这是一个 workaround 。
在这里 streaming render 是基于 react 18 Suspense
来说的,Suspense 下的组件会被 streaming 流式传输渲染,做到更小的体积,更快的响应、渲染速度,更好的体验。
值得一提的是和以往 Suspense
认知的区别。
在以前的 experiment 阶段我们更多的是使用
+ React.lazy()
+ webpack lazyimport
来做拆包:
import React, { Suspense, lazy } from 'react'
const Component = lazy(() => import('./Component'))
function Page() {
return (
<Suspense fallback={<LoadingElement />}>
<Component />
<Suspense/>
)
}
如此一来 webpack 便会配合我们把 lazyimport 的 ./Component
的 .js
产物拆出去按需加载,而 React Suspense 会配合我们在未加载完成的时候 fallback
显示加载状态。
而在 react 18 中 Suspense
被正式化后,我们的着眼点便是如何利用 Suspense
带来的 streaming render 做应用体验上的优化,在 http 基础中我们已经知晓过 streaming transport 的优点,那结合 react 落地到实际中便是 nextjs 正在赋能的聚焦点,这带来了更快的 Suspense
组件响应速度,以及根据用户交互优先级的选择性水合(比如优先传输正在交互 hover 的组件)。
回归实践,我们要认识到的是,以下三种 Suspense 方式均会在 streaming 中被打开:
import dynamic from 'next/dynamic'
import { lazy, Suspense } from 'react'
import Content from '../components/content'
// These two ways are identical:
const Profile = dynamic(() => import('./profile'), { suspense: true })
const Footer = lazy(() => import('./footer'))
export default function Home() {
return (
<div>
<Suspense fallback={<Spinner />}>
{/* A component that uses Suspense */}
<Content />
</Suspense>
<Suspense fallback={<Spinner />}>
<Profile />
</Suspense>
<Suspense fallback={<Spinner />}>
<Footer />
</Suspense>
</div>
)
}
此处就和 webpack lazyimport 没有那么强的配合了,即使是 sync 同步的,也会被 streaming 。所以在 react 18 SSR
场景中应该尽可能多的使用 Suspense
。
注:
以上内容被使用前还要遵循一定的前置条件,以及 nextjs 对 streaming 的支持达到成熟阶段。
关于 streaming 更多的介绍,你可以在 官方文档 阅读到更多。
对于 RSC
来说,虽然至今为止掀起了很久的讨论潮,但往往都局限于 很浅的理论层 ,甚至没有相应 code 的表述。到 nextjs 中实践来说,当前 RSC
实现中存在的限制也十分多,可调用的 api 屈指可数甚至没有,仍然只是 demo 阶段。
那么我们对 RSC
的认知是一个:能肩负服务端昂贵计算、利用服务端环境的组件提供者。
服务端计算:这一点没有什么额外新意值得描述,最终归宿是提升性能和体验。
服务端环境:这意味着可以进行 nodejs 的 api 调用,鉴权、先置运行保密逻辑等。
得益于 react 18 的 streaming 传输特性,RSC
在大体积组件的传输上有独特的优势,也就是将某些重型依赖做成 RSC
来流式传输提升性能和体验。
React 团队侧计划在 react 18 的某个 minor 阶段开始推进正式的 RSC
,由于业界讨论十分混杂,本文也在此处不多加展开,我们进一步期待新的 react 蓝图。
关于 nextjs 在 RSC
已有的实践落地 demo,你可以在这里查看:
在本文描述中,除了 ISR
外均为较新的 experiment 功能,我们进行了 “认知上的浅尝辄止” 。
继 react hooks 鼻祖跳槽 vercel 后,逐渐出现了 vercel nextjs 做什么,react 就配合出什么的趋势,未来 nextjs 的一切也将成为影响 react 发展的重要一环。