We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
现有的服务端渲染(Server-side rendering,简称 SSR)的原理是当 HTML 请求到达 Node 端时先等待后端接口数据请求完成(30~300ms),然后再进行渲染(2~5ms),最后再响应渲染完成的页面给浏览器。
大致流程是: fetch data (server) → render to HTML (server) → load code (client) → hydrate (client)
如本文用作示例的商品管理页面,需要并发8个后端接口请求,最慢的接口 /api/xxx/goodsList 延时为 246.6 ms,导致Step1阶段用户看到的页面白屏时间至少是 246.6ms + 5ms。
商品管理
/api/xxx/goodsList
Step1阶段
💡 Step2 截图为灰色仅为了区别于 Step3 可交互状态,实际用户看到的效果与 Step3 无差异
为了解决后端接口延时不可控造成的 Step1 阶段白屏时间过长的问题,于是我们开发了渐进式渲染功能,优化后的渲染链路变成了如下
渐进式渲染
React18 新的 Suspense SSR 架构允许你在服务端使用 Suspense 组件,比如你的 Comments 组件是需要后端接口的数据,那么可以做到后端接口数据仅阻塞 Comments 组件,不会阻塞整个 App 组件的渲染与提前返回
Suspense
Comments
App
<Layout> <NavBar /> <Sidebar /> <RightPane> <Post /> <Suspense fallback={<Spinner />}> <Comments /> </Suspense> </RightPane> </Layout>
新 Suspense SSR 架构下的渲染链路变成了如下
你可能想到部分可交互状态时,如果客户端其他组件响应了事件导致 Comment 组件的 props 变化,而服务端是根据 initProps 对 Comment 进行的渲染,那么 React 会如何取舍
Comment
function Content() { const [count, setCount] = useState(0); return ( <Layout> <NavBar /> <Sidebar /> <RightPane> <Post /> <h2 onClick={() => { setCount(count + 1) console.log("setCount 点击事件测试, count: ", count); }} > setCount 点击事件测试 </h2> <Suspense fallback={<Spinner />}> <Comments count={count}/> </Suspense> </RightPane> </Layout> ); } function Comments({ count }) { const comments = useData(); return ( <> <span>count: {count}</span> {comments.map((comment, i) => ( <p className="comment" key={i}> {comment} </p> ))} </> ); }
从测试结果来看 Props 发生变化后 React 会以客户端最新渲染的结果为准, 与此同时抛出Uncaught Error: This Suspense boundary received an update before it finished hydrating.错误
Uncaught Error: This Suspense boundary received an update before it finished hydrating.
因为 Suspense 支持对于单个组件进行的延迟渲染,首先我们需要对页面组件进行拆分,同时使用 Suspense 进行包裹
如果升级到了新 Suspense SSR 架构下的渲染链路变成了如下
如果发现升级后页面没有进行分块渲染, 或许你要继续阅读 👉 服务端流式渲染 iOS 中踩坑记
The text was updated successfully, but these errors were encountered:
因为推荐类接口耗时都比较长,被迫从 SSR 出内容改成了 SSR 出骨架屏,hydration 后再从 client 发起请求。 这个新的架构下就可以更早发起接口请求了👍🏻
Sorry, something went wrong.
推荐类 Case 较低成本的是先实现我文章所提到的渐进式渲染,接口在内网聚合还是比客户端快不少
Suspense SSR 在 Next.js 中也只是半成品,如下图,Next.js 实现是等所有 Suspense 组件就绪后最后统一发送给客户端,没有做到按需。我这两天在内部SSR框架实现了一版,发现 React18 支持的东西太少,大都需要框架自行去实现解决 😓
Suspense SSR
按需
No branches or pull requests
目录
1. 实际业务的困境
现有的服务端渲染(Server-side rendering,简称 SSR)的原理是当 HTML 请求到达 Node 端时先等待后端接口数据请求完成(30~300ms),然后再进行渲染(2~5ms),最后再响应渲染完成的页面给浏览器。
如本文用作示例的
商品管理
页面,需要并发8个后端接口请求,最慢的接口/api/xxx/goodsList
延时为 246.6 ms,导致Step1阶段
用户看到的页面白屏时间至少是 246.6ms + 5ms。为了解决后端接口延时不可控造成的 Step1 阶段白屏时间过长的问题,于是我们开发了
渐进式渲染
功能,优化后的渲染链路变成了如下2. Suspense SSR 架构
React18 新的 Suspense SSR 架构允许你在服务端使用
Suspense
组件,比如你的Comments
组件是需要后端接口的数据,那么可以做到后端接口数据仅阻塞Comments
组件,不会阻塞整个App
组件的渲染与提前返回新 Suspense SSR 架构下的渲染链路变成了如下
2.1. 可能存在的问题
你可能想到部分可交互状态时,如果客户端其他组件响应了事件导致
Comment
组件的 props 变化,而服务端是根据 initProps 对Comment
进行的渲染,那么 React 会如何取舍从测试结果来看 Props 发生变化后 React 会以客户端最新渲染的结果为准, 与此同时抛出
Uncaught Error: This Suspense boundary received an update before it finished hydrating.
错误3. 应用到业务中的效果
因为 Suspense 支持对于单个组件进行的延迟渲染,首先我们需要对页面组件进行拆分,同时使用 Suspense 进行包裹
如果升级到了新 Suspense SSR 架构下的渲染链路变成了如下
4. 小结
Suspense SSR 架构解决了服务端渲染各个流程串行等待问题,强调一切按需(懒加载,懒编译,现在是懒渲染?)进行
渐进式渲染首屏比 Suspense SSR 更加完整
Suspense SSR 类似于懒渲染,设计理念更加符合现代化 Web 开发
5. 最后的话
如果发现升级后页面没有进行分块渲染, 或许你要继续阅读 👉 服务端流式渲染 iOS 中踩坑记
The text was updated successfully, but these errors were encountered: