Skip to content
New issue

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

前后端渲染之争 #5

Closed
camsong opened this issue Apr 6, 2017 · 20 comments
Closed

前后端渲染之争 #5

camsong opened this issue Apr 6, 2017 · 20 comments

Comments

@camsong
Copy link
Contributor

camsong commented Apr 6, 2017

本期精读的文章是
https://medium.freecodecamp.com/heres-why-client-side-rendering-won-46a349fadb52

十年前大多数网站都是使用服务端渲染,但近些年客户端渲染越来越普遍,本文从客户端渲染的各种优势说明为什么客户端渲染会胜出。建议结合客户端渲染中的同构渲染一起来讨论。

@camsong camsong changed the title 优雅编码最佳实践 前后端渲染之争 Apr 7, 2017
@monkingxue
Copy link

知乎 - 这就是客户端渲染胜利的原因
简单地翻译了一下原文,权当抛砖引玉了

@BlackGanglion
Copy link
Contributor

BlackGanglion commented Apr 8, 2017

从服务端渲染,到客户端渲染,再到同构渲染。我们一直从开发者和用户两个不同的维度进行思考和探索。服务端渲染时代,从工程角度而言,前后端代码耦合在一起,包含有上下游依赖,而对用户而言,每一次交互都相当于重新刷新页面。进入客户端渲染时代,前后端完成解耦,能够并行开发、并行部署,后端专注于服务化,接口可被多端复用,而前端专注于界面与交互,例如局部更新、懒加载等等。

那么客户端渲染就是一个完美的方案了么?显然不是,Isomorphic JavaScript: The Future of Web AppsWhy Everyone is Talking About Isomorphic / Universal JavaScript and Why it Matters 两篇文章中均指出其面临的三个问题:SEO、首屏性能及可维护性。这都可以由同构渲染来解决。同构渲染的原理很简单,即 node 服务端与客户端复用同一套代码,由服务端来完成应用的首屏渲染。但在实际应用中却遇到了非常多的问题,具体可由 @javie007 来谈谈

@ascoders
Copy link
Owner

ascoders commented Apr 9, 2017

也可以参考我在百度做的后端渲染经验总结:ascoders/blog#6

@fanhc019
Copy link
Contributor

fanhc019 commented Apr 9, 2017

就现在(2017.04)而言,客户端渲染确实不是一个很新的概念了,我曾在两年前翻译过一篇同构的文章。简单来说,我们把渲染逻辑交给前端来做的方式叫做 PWA,而前端页面渲染又会有很多弊病,需要服务端渲染来配合完成,同构的概念也应运而生了。

借助于 JavaScript 可以在客户端和服务端运行的优势,很容易就实现了一次编码多端执行。这样我们就做到了,既能享受到客户端渲染带来的局部刷新、懒加载、富交互、CDN 部署、关注度分离的优势,又通过服务端渲染弥补了SEO 和首屏渲染的问题。这篇文章将 SPA 和服务端渲染的优劣总结的很到位,而且还提到了 PWA,真的很棒。

SPA 做到了近似 App 的体验,但还是很依赖网络贷款和浏览器环境。而 PWA 借助了 Service Worker 和 Notification API 实现了让应用离线和消息推送,让用户可以在无网络的环境下继续浏览已缓存的文件,同时可以体验到原生 App 的特性。如果说 SPA 结合了客户端渲染和服务端 SEO 的两种优势,那么 PWA 就是同时结合了 web 开发的便利和 Native App 稳定的特性。

@FrankFang
Copy link

想得有点美。
Talk is cheap, show me your page.

@ascoders
Copy link
Owner

ascoders commented Apr 9, 2017

http://tieba.baidu.com/n/tbhighh5/album/456900832689737361 ,开启后端渲染
http://tieba.baidu.com/n/tbhighh5/album/456900832689737361?nsr=1 ,关闭后端渲染
这里给大家一个 demo 页面

@camsong
Copy link
Contributor Author

camsong commented Apr 9, 2017

@monkingxue 感谢你的翻译

同构渲染从 14 年底伴随着 React 的崛起而被认为是前端框架应具备的一大杀器,以至于当时很多人以此来抛弃 Angular 1 转向 React。然而近 3 年过去了,好像很多公司逐渐从全栈同构的理想化逐渐转到首屏或部分同构甚至放弃同构。当时冒出的一大批同构的轮子也大多不再更新 derbyrendrreact-isomorphic-starterkit。归根结底就是为了同构所需要花费的代价太大。

但16年底 Next.js 发布之后,我觉得可能会有所改变,Next.js 有以下几个亮点特别吸引我:

  1. 巧妙地用标准化的解决了请求的问题。同构和页面开发类似,异步是个大难题,异步中难点又在接口请求。Next.js 给组件新增了 getInitialProps 方法来专门处理初始化请求,再也不用手动往页面上塞 DATA 和调用 ReactDOMServer.renderToString
  2. 使用 styled-jsx 解决了 css-in-js 的问题。这种方案虽然不像 styled-component 那样强大,但足够简单,可以说是最小的成本解决了问题
  3. Fast by default。页面默认拆分文件方式打包,支持Prefetch页面预加载
  4. 全家桶式的的解决方案。简洁清晰的目录结构,这一点 Redux 等框架真应该学一学。不过全家桶的方案比较适合全新项目使用,旧项目使用要评估好成本

另外值得一提的是,Next.js 的作者之一就是大名鼎鼎的 Socket.io 的作者 Guillermo Rauch。我想应该是目前支持同构成本最低的方案之一了。

@FrankFang
Copy link

为了同构而同构,就很可笑。
软件开发多少年来就是「什么语言适合,就用什么语言」。
我从未看出同构之后有什么特别大的好处。
要真说同构,Rails 早就基本做到了同构了,coffee 和 sass 都是类 ruby 的 DSL,起步比前端社区不知早多少年,而且已经很成熟了。

@alcat2008
Copy link

既然提到前后端渲染之争,那么前后端为什么要 “争”,“争” 的又是什么?答曰:用户体验

在不同的历史时期,不同的外部环境下,面对复杂的软硬件环境、网络环境,前后端承担的职责必然有所区别。

在 16K 时代,带宽的 “宽” 是假的,想想当时的 QQ,前端能渲染什么呢,而且当时的系统配置普遍偏低,浏览器能做的也就是渲染简单的静态 HTML 元素,js 处理能力也处于较低水平,那个时候服务端不得不做的更多来支撑用户的使用。这种情况一直持续了好多年,直到基于 V8 引擎的 Chrome 面世,js 迎来了蓬勃的发展期,进而赋予了浏览器更多的可操作性。

时至今日,单页面应用 SPA 大行其道,都是 js 健壮发展的结果。对 SPA 而言,一个无法忽视的问题就是 bundle 文件太大(其它问题暂且不论),导致了白屏时间,特别是网速差的时候,让用户等待白屏结束并非一个很好的体验。

所以前后端同构出现了,其问题的焦点之一就是减少这个白屏时间。引发白屏时间问题的关键有两点,一是网速,二是 js/浏览器 的处理能力。

随着 HTTP/2 及前端的发展,这种同构所带来的收益将逐渐被弥补,在恰当的时候,这种不得不委以他人的无奈会被前端自我消化。届时,新的 “争” 会出现,共同推动行业的迭代发展。

@arcthur
Copy link

arcthur commented Apr 10, 2017

想到一部分,先说下,后面再补充

client render 好处在文章中都写了,client render 比较大的问题就是首次渲染SEO
universal 是对于现在 SPA 应用的。但如果 universal all page 会带来很多问题。我认为较大的两个问题是缓存与首次动效。第一缓存是根据应用的特征来定的,有很多应用作页面缓存的成本很高,有非常多的条件,我们团队就碰到这个问题,其它同学可以详细补充下这块。第二是动效,如果我们从 server 拿到渲染好的内容那么原来的如果作了 fold 这样的动效可以做,但这只是首次渲染要做的,不是每次都有。

针对 universal 也是经过一些反馈思考,我的观点是该用的一定要用,但只用在头部导航或重要的首屏区域。为什么呢?我们针对首次渲染SEO两个问题来看下我的想法。

首次渲染的问题可以用更好的交互来解决,先看下 linkedin 的渲染

有什么感受,非常自然,打开渲染并没有白屏,有两段加载动画,第一段像是加载资源,第二段是一个加载占位器,过去我们会用 loading 效果,但过渡性不好。近年流行 Skeleton Screen效果。其实就是�加载无法避免的时候,为了解决等待加载过程中白屏或者界面闪烁造成的割裂感带来的解决方案。

SEO 的问题,有两点:

  1. 你的应用是否都要做 SEO,如果你是一个后台应用,那么只要首页做一些静态内容宣导就可以了。如果是内容型的网站,那么可以考虑专门做一些页面给搜索引擎
  2. google 2015 就出了一个 scheme 第一最好改成 pushstate 来做,用 hashstate 当然也可以做,不同的搜索引擎的爬虫还不一样,要做一些配置的工作,而且可能要经常关注数据,有波动那么可能就需要更新。第二是该做 sitemap 的还得做。其它的搜索引擎像 baidu 就不太清楚了,相关同学知道可以补充。

做 SEO 一定有更多方法,client render 是未来的趋势,相信搜索引擎也在进货。

像做这种判断一定是看应用的,前面同学也有说到,如果今天我工作的重心是内容型产品,那么我会考虑是否用 react 或 angular 这样的框架来做,或是做一些区分。

@monkingxue
Copy link

之前负责过一个纯后端渲染完全重构成前端渲染的项目,白屏问题确实比较严重,当时的解决方案是利用 router 进行 bundle 的分拆,加上一些 loading 和 transition,这样的体验提升与同构不分伯仲,而且相对来说破坏性的修改比较小,难度也小一点。当然,将这两者结合的方案也可以考虑一下。所以我对于同构的态度还是:先说为什么,再说怎么做。关于 SEO,我做 webAPP 的情况比较多,对于 SEO 的需求不高,因为导流的方式并不是搜索,对于这个就不太了解了。

关于 RoR 的 问题,确实 Ruby 的开发社区大牛很多,开发了很多很棒的轮子,我也写过一段时间的 Ruby,包括coffeescript 和 sass ,也手写过玩具级别的 DSL(编译到 JS,主要是增加了CPS和一些语法糖),感觉一项技术的发展除了要有先进的理念以外,还是需要社区应用的推进。比如最早的CPS就有实现 yield,也跟着能实现 async/await,但是这些东西并没有出现(当然 C#后来这么做了),因为没有需求的推动啊。

学习别的社区的优秀特点不是什么坏事情(比如 C++),但是把这些优秀的特性本地化并对原有技术方案进行替换升级才是坠吼的。JS 社区有些人比较喜欢鼓吹 “JS No.1” 确实不可取,但也不用妄自菲薄,我相信 JS 能变得更好,也能为 PL 社区输出属于自己的贡献。

@javie007
Copy link

最近在项目里把玩了下SSR,我重点提提SSR过程中要注意的地方吧。

背景

我们的项目基本上是客户端渲染,而后端原来是用模版引擎做了个简单的页面,不是完全静态,会塞一些数据到前端,比如版本号,像这样:

<html>
  ...
  <script src="/xx/$!jsVersion/app.js" ></script>
</html>

由于这个文件过大,在刷新页面时,前端会有一个白屏时间。所以尝试着先在服务端渲染把页面渲染出来,在这其中也遇到一些tricky的问题,下面我仔细说下。

问题

性能

你把原来放在几百万浏览器端的工作拿过来给你几台服务器搞,这还是花挺多计算力的。
这方面调优,可以参考walmart的调优策略

mock浏览器环境

由于客户端代码使用的window在node环境是不存在的,所以要mock window,其中最重要的是cookie,userAgent,location。
但是由于每个用户访问时是不一样的window,那么就意味着你得每次都更新window。

而后端由于require的cache机制,造成前端代码除了具体渲染部分都只会加载一遍。
这时候window就得不到更新了。所以要引入一个合适的更新机制,比如把读取改成每次用的时候再读取。

OOM

前端代码由于浏览器环境刷新一遍内存重置的天然优势,对内存溢出的风险并没有考虑充分。
比如在componentWillMount里做绑定事件就会发生内存溢出,因为react的设计是后端渲染只会运行componentDidMount之前的操作,而不会运行componentWillUnmount方法(一般解绑事件在这里)。

异步操作

由于React后端渲染是同步的,所以异步操作是得不到运行的,这时候可以参考下next.js的做法。

simple store(redux)

这个store是必须以字符串形式塞到前端,所以复杂类型是无法转义成字符串的,比如function。

思考

所以做SSR之前,一定要充分考虑到浏览器端和服务器端的环境差异,才能避免踩坑。

@FrankFang
Copy link

styled-jsx 这种明显是临时方案的方案居然有这么多人用……语法好丑

@jasonslyvia
Copy link
Contributor

@camsong 分享的这篇文章很好的阐明了为什么客户端渲染是更优秀的选择(当然其中还顺便安利了一把 JavaScript 优越的跨平台特性),但有意思的是这篇文章的 top 评论也给出了不少为什么服务端渲染同样优秀的理由,我摘录如下:

  • 服务端渲染可以做到更细粒度的缓存控制(页面上公共的部分一次渲染后后续直接输出)
  • 服务端渲染不用关心浏览器兼容性问题
  • 服务端渲染不需要先下载一堆 js 和 css 后才能看到页面(首屏性能)
  • 对于电量不给力的手机或平板,减少在客户端的电量消耗很重要

都是很强有力的理由。

同时我注意到,cam 提出了「服务端渲染」和「客户端渲染中的同构渲染」这两个不同的概念,一个对应的是传统 Java、PHP 或 ASP 的渲染机制,另一种则是使用 node.js(理论上可以使用其他语言)把在客户端使用的组件在服务端渲染成 HTML 文本。

我的观点是:截止目前为止,同构渲染在大型项目中不具备足够的应用价值 ,理由如下:

我的观点并非不认可同构渲染,而是认为综合目前的技术发展水平和现状,同构渲染实施难度大、对现有代码库改造多、投入产出比不高。

同构解决方案不够优雅且困难重重

何谓优雅,即你的方案无论是在前端使用还是在服务端使用,都不需要太多的额外改造。

上面很多朋友也说过自己的尝试,我在两年前也对同构渲染进行过研究,但也是浅尝辄止。@javie007 已经给出了目前 SSR 的最佳实践,不得不说,时至今日这种解决方案依然算不上「优雅」。

这是什么问题造成的呢?很简单,就是我们的前端代码在编写时并没有过多的考虑后端渲染的情景,因此各种 BOM 对象和 DOM API 都是拿来即用。这从客观层面也增加了同构渲染的难度。

同构更复杂的问题应该是发送请求以及请求合并,这个问题相比 @javie007 是深有体会的,这里不再展开。

同构渲染定位尴尬

何谓内容型网站,即网页的主题是内容,如各大门户网站。这样的网站是有强烈的 SEO 需要,同时页面加载速度也是爬虫对页面排名衡量的重要标准之一,因此在服务端渲染是很有必要的。

注意,我在这里说的是「服务端渲染」。因为,对于这种类型的网站,通常也不会有特别复杂的交互,因此传统的「服务端渲染」足以应付。

反过来看非内容型的网站,交互足够复杂,组件库庞大,使用客户端渲染没有问题。但是把这种交互复杂的网站強搬到服务端进行渲染,又会遇到第一个观点中提到的各种问题。

不难看出,「同构渲染」在不同类型的网站中定位都很尴尬。

同构渲染存在大量的边际成本

同构渲染的潜台词就是,由前端工程师来控制页面最终产出的过程,这也意味着前端工程师在写组件、调样式、拼页面的同时,还需要学习 node.js 应用的编写、维护、调试、调优、监控等各种能力。

这里不是说前端工程师学不会或者不好学会这些技能,而是站在更高的层面考虑,在新项目中采用同构技术会存在着诸如此类此类风险。同时,同构渲染意味着 node.js 应用控制着整个应用的最终呈现,一旦 node.js 应用挂了,那就意味着整个应用都挂掉了,即使后面的 API Server 和数据库并没有出现问题。

因此在采用同构渲染时,除了要考虑同构渲染技术本身的各种技术实现,还需要考虑因为采用了同构渲染技术而带来的边际成本,这种成本团队是否可以接受并处理。

当然,如果是个人玩票项目此项可忽略不计。

小结

同构渲染很美,虽然有很多问题,但不影响我们摸索和尝试。

@twobin
Copy link
Contributor

twobin commented Apr 11, 2017

Google 提出的 PRPL 模式,是一种用于结构化和提供渐进式网络应用的模式,强调应用交付和启动的性能。目前我们的同构策略正在借鉴这一策略,在单页面应用中,剥离出尽可能小的入口文件,避免加载一个大文件而导致首屏白屏时间过长,保证 ssr 的效率,动态异步加载其他文件。
关于同构我们正在实践中,如果成果显著,后面可以详细分享我们的具体实施方法。
prpl

PRPL模式 https://developers.google.cn/web/fundamentals/performance/prpl-pattern/

@xile611
Copy link
Contributor

xile611 commented Apr 12, 2017

在开发 recharts 的时候也碰到过很多 SSR 相关的问题。对这些问题进行归纳主要有三种:

  1. document 等对象找不到的问题
  2. DOM计算报错的问题
  3. SSR 与 CSR 渲染内容不一致的问题

其中,问题1、2主要是因为我们在SSR的时候调用了 DOM API 导致,所以我们在相应的 地方增加了一个 isSsr 的判断:

export const isSsr = () => (
  !(typeof window !== 'undefined' && window.document && window.document.createElement &&
    window.setTimeout)
);

但是这个判断的问题是,在特定的同构环境下,用户可能对这些 DOM API 都进行了 mock ,导致 isSsr 被误判,所以在同构项目中需要考虑的一个问题是: 如果让代码正确的判断当前运行环境是 SSR 还是 CSR ?

对于问题3,原因是很多 DOM 计算在 SSR 的时候是无法进行的,涉及到 DOM 计算的的内容不可能做到 SSR 和 CSR 完全一致,这种不一致可能会带来页面的闪动。

@ascoders ascoders mentioned this issue Apr 12, 2017
65 tasks
@arcthur arcthur closed this as completed Apr 14, 2017
@Lucifier129
Copy link

Lucifier129 commented Apr 15, 2017

同构也可以扬长避短,从架构设计层面做好隔离;同构渲染多少,做不做服务端渲染,都是可控的。既是单页应用,也是多页应用。第一次访问是服务端渲染,交互时是客户端渲染。

详情见:
《IMVC(同构 MVC)的前端实践》

@Exodia
Copy link

Exodia commented Apr 15, 2017

在没有bigpipe结合的SSR方案下,同构未必有性能上的优势,因为需要将数据完全请求并渲染后吐出,另一个研发效率层面的问题在于,有很多大型的历史项目不是那么容易的切换底层框架,因此我们自己探索了一个首屏渐进式数据预加载的方案。有兴趣的参考并讨论下:)

http://www.infoq.com/cn/articles/baidu-ssp-single-page-app-performance-optimize-practice

@Lucifier129
Copy link

Lucifier129 commented Apr 15, 2017

@Exodia 仅仅同构渲染,性能比不上纯粹的服务端渲染,因为后者没有同构的束缚,可以更自由地考虑性能因素。

不过,IMVC 是“第一次访问是服务端渲染,交互时是客户端渲染。”,相比纯服务端渲染的多页应用,它用客户端路由节省了交互阶段的服务端渲染开销;相比纯客户端渲染,它又解决了第一次访问的首屏渲染和 SEO 问题。

所以平均而言,同构可能得到更好的性能。

@wuzinong
Copy link

wuzinong commented Dec 3, 2019

后端截取useragent如果是爬虫可以调headless chrome再获取渲染后的内容返回给搜索引擎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests