渲染的概念
在Web开发中,渲染(Rendering)是一个核心概念,指的是将应用程序的数据(data)与模板(template)结合,生成最终的HTML页面,这个页面随后会被浏览器解析并展示给用户。这个过程是动态Web应用的核心,因为它允许开发者根据不同的数据动态地生成页面内容,而不是每次更改都需要手动编写静态的HTML文件。
我们可以简单地把渲染理解为:渲染就是将页面数据和页面模版组装成html的过程
也就是: data + template = html,当然data也可能什么都没有,没有数据也是一种数据
-
客户端渲染就是
data + template = html
过程在客户端进行,服务器直接转发静态 html 资源即可 -
服务端渲染就是
data + template = html
过程在服务端进行,客户端不需要渲染页面 -
静态化渲染就是打包的时候进行
data + template = html
过程,然后客户端在请求时,服务端不做任何处理,直接以原文件的形式返回给客户端,客户端获取到页面后,在加载完 JS 后才通过 JS 来渲染页面内容。
单页应用和多页应用
-
单页应用:
-
只有一个页面,可以部分更新,通过vue 的diff对比树结构的不同而部分更新,点击跳转新页面并不产生新的html,所以跳转时缓存不会消失,不需要把数据存在本地里面
-
-
多页应用:
-
有多个页面,每一次跳转都会生成新的html,整棵树都会重新构建,而不是部分更新,state一刷新就没了,所以就经常要把数据存在localstorage、sessionstorage、cookie里面
多页面应用(MPA) 单页面应用(SPA) 组成 一个外壳页面和多个页面片段组成 多个完整页面构成(但外观看起来只有一个页面) 资源共用 不共用,每个页面都需要加载CSS和JS 共用,只需在外壳部分加载CSS和JS 刷新方式 整页刷新 页面局部刷新或更改 URL模式 a.com/pageone.html
,a.com/pagetwo.html
等a.com/#/pageone
,a.com/#/pagetwo
等(Hash模式)或a.com/pageone
, a.com/pagetwo(History模式,需要服务器支持)用户体验 页面切换加载缓慢,流畅度不够 页面片段间的切换快,用户体验良好 转场动画 容易实现 容易实现(但SPA通常提供更好的转场效果) 数据传递 依赖URL传参、cookie、localStorage等 容易,可以通过 JavaScript 在内存中直接传递 搜索引擎优化(SEO) 较差,需要服务器端渲染(SSR)优化 实现方法较为困难,不利于SEO检索(但可通过预渲染等技术改善) 适用范围 适用于追求高度支持搜索引擎的应用 高要求的体验度、追求界面流畅的应用 开发成本 较低,但页面重复代码多 较高,常需借助专业的框架和库 维护成本 相对复杂 相对容易(一旦架构搭建好,后续维护较为简单) -
页面的渲染流程
1、浏览器通过请求得到一个HTML文本
2、渲染进程解析HTML文本,构建DOM树
3、解析HTML的同时,如果遇到内联样式或者样式脚本,则下载并构建样式规则(stytle rules),若遇到JavaScript脚本,则会下载执行脚本
4、DOM树和样式规则构建完成之后,渲染进程将两者合并成渲染树(render tree)
5、渲染进程开始对渲染树进行布局,生成布局树(layout tree)
6、渲染进程对布局树进行绘制,生成绘制记录
7、渲染进程对布局树进行分层,分别栅格化每一层,并得到合成帧
8、渲染进程将合成帧信息发送给GPU进程显示到页面中
渲染模式分类
-
CSR:
Client Side Rendering
,客户端(通常是浏览器)渲染 -
SSR:
Server Side Rendering
,服务端渲染 -
SSG:
Static Site Generation
,静态网站生成 -
ISR:
Incremental Site Rendering
,增量式的网站渲染 -
DPR:
Distributed Persistent Rendering
,分布式的持续渲染
从 CSR 到 SSR
CSR客户端渲染
当用户访问某个URL时,服务端返回的HTML是不包含页面具体内容的骨架,然后由客户端的JavaScript来完成页面的渲染。
对于使用Vue或者React构建的单页面web应用,当我们打开浏览器访问页面时服务器一般只会返回一个比较简单的html模版,之后浏览器再加载相应的js并进行解析,生成dom元素将页面渲染出来。
普通的单页应用只有一个 HTML,在浏览器请求页面时,服务端只会生成一个无具体内容的 html 文件,之后都是浏览器通过加载并执行 chunk.js 这种 JavaScript 去动态更新,所以无法被爬虫识别,不利于SEO。
这种渲染方式虽然在后续的页面切换速度很快,但是也明显存在两个问题:纯客户端的 SPA 在首屏加载和 SEO 方面有显著的问题,因为浏览器会收到一个巨大的 HTML 空页面,只有等到 JavaScript 加载完毕才会渲染出内容,白屏时间过长且 SEO 不友好。
优点:
1、响应速度快:一旦HTML文件加载完成,浏览器就可以开始渲染页面,而不需要等待服务器返回完整的渲染结果
2、动态性强:页面渲染在客户端进行,因此可以方便地实现更好的交互性和动态效果
3、前端部署简单:只需要一个静态服务即可部署前端代码,降低了部署成本
4、减轻服务端的压力,因为大部分渲染工作都在客户端完成
缺点:
1、首屏加载慢:需要等待 JavaScript 下载和执行,可能导致首屏加载时间较长,特别是对于复杂的单页应用(SPA)
2、不利于SEO:有些搜索引擎爬虫无法执行 JavaScript,看不到完整的程序源码,获取不到页面关键信息
3、白屏时间:在JavaScript代码加载和执行期间,用户可能会看到空白的页面,即所谓的“白屏时间”
SSR 最早是为了解决单页应用SPA产生的 SEO、首屏渲染时间等问题而诞生的。
SSR服务端渲染
当用户访问某个URL时,服务端实时生成包含内容的HTML文件,返回给浏览器解析后能直接构建出有内容的页面。
页面html先在服务端渲染完成,此刻得到的一堆的html字符串,返回给浏览器后便可直接进行解析渲染,完成后用户便可看到完整的页面,大大减少了白屏时间,之后再加载js,使网页可进行正常交互。
在服务端直接实时同构渲染用户看到的页面,整个查数据库的过程以及 html 都由服务端生成,客户端只需要识别返回的一大堆 html 代码。能让服务器直接返回渲染好的 HTML,让用户在 JavaScript 下载完毕前就看到页面内容,提高用户使用体验。
这种渲染方式需要一个服务器承载页面的实时请求、渲染和响应,增大了服务端的开发和运维的成本。对于一些较为静态场景,比如博客、官网等,它们的内容相对确定,变化不频繁,每次请求都会重新生成页面,且每次通过服务端渲染出来的内容都是一样的,浪费了很多没必要的服务器资源。
优点:
1、首屏加载速度快:服务器已经生成了完整的HTML页面,可以生成缓存片段,因此客户端可以直接显示这个页面,无需等待JavaScript加载和执行
2、SEO友好:搜索引擎爬虫可以直接看到渲染好的 HTML,有利于SEO优化
3、适合复杂页面:对于包含大量数据、需要复杂计算的页面,SSR可以更好地处理并减少客户端的负载
缺点:
1、服务端压力大:对于每个请求,服务器都需要重新渲染页面,这可能导致服务器压力过大,可以用静态化来解决
2、无法交互:SSR 是直接产出 HTML 的代码,DOM 元素事件绑定的逻辑仍然需要 JS 才能够完成,因此页面不可以交互
解决方案:SSR页面引入CSR的脚本(同构)。在实际场景中,会在SSR页面中加入CSR的脚本,完成DOM的事件绑定,这个完成事件绑定的过程,也被称为 Hydration。
3、传统服务端渲染的用户体验较差、不容易维护,通常前端改了部分 html 或者 css,后端也需要修改
4、开发调试困难:开发要考虑到服务器端和客户端环境的差异,调试要考虑到服务器端和客户端的日志和错误信息
从 SSR 到 SSG
SSG在构建的过程当中,也就是当执行
npm build
时,就可以生成完整的HTML内容,构建完成后进行HTML部署,在生产环境下就不需要服务器的开发、运维相关的工作,研发和运维的成本会比SSR低一些
SSG静态页面生成
在构建时预先渲染页面并生成静态的 HTML,把生成的HTML静态资源部署到服务器后,用户访问某个URL时,服务端直接返回包含页面内容的HTML即可。
在项目构建时生成包含内容的html,之后将相应的html、js、css等静态资源发布到相应的CDN节点,这样当用户进行访问时页面可直接渲染。
服务端会预加载,先把所有 html 页面都生成好并放到CDN上,在客户端需要的时候直接返回,不需要服务器实时渲染和响应,大大节约了服务器运维成本和资源,比 SSR 少了一个去数据库CRUD的操作,效率更高,SEO友好,但是只适合页面内容较为静态的场景,比如官网、博客等,因为服务端渲染可以提前完成。
但它对服务器的要求很高,因为如果有成千上万个页面,它就要预先生成成千上万个模板,需要耗费很多服务器资源。难以应对高度动态的内容或页面数量很多的情况,因为在静态构建时不能拿到最新的数据,也无法枚举海量页面。
优点:
1、性能卓越:页面是静态的,因此无需等待服务器渲染,直接由浏览器加载显示,具有极快的页面加载速度
2、安全性高:服务器只提供静态文件,因此降低了遭受攻击的风险
3、服务器压力小,减轻服务端的渲染压力,继承了SSR首屏性能以及优秀的SEO支持
4、适合内容型网站:对于数据变化频率较低的内容型网站(如博客、文档网站等),SSG是一个很好的选择
缺点:
1、动态性受限:页面是静态的,因此难以实现复杂的动态交互效果,不适用于数据经常变化的场景
2、构建时间长:对于频繁更新数据的大型网站不太适合,因为需要重新构建和部署整个网站,构建时间可能会比较长
3、不适合频繁更新:对于需要频繁更新数据的网站,SSG可能不太适合,因为每次更新都需要重新构建并部署整个网站
SSG 虽然可以很好的将页面静态资源进行提前进行构建并部署到CDN来提高用户访问效率,也很好的解决了白屏时间过长和 SEO 不友好的问题。但若要生成的页面过多,比如有十几万个,那么很难在服务端一次性生成,全量预渲染整个网站是不现实的,这时,就需要使用到ISR做增量渲染了。
从 SSG 到 ISR/DPR
ISR增量渲染
Next.js 推出的 ISR(Incremental Static Regeneration) 方案,是基于SSG进行实现的,对比SSG只是增加了Server端的逻辑。
允许在应用运行时再重新生成每个页面 HTML(允许网站在不需要重新构建整个站点的情况下,仅对发生变化的部分进行更新和重新生成)。这样即使有海量页面,也能使用上 SSG 的特性。一般来说,使用 ISR 需要 getStaticPaths
和 getStaticProps
同时配合使用。
-
关键性的页面(如网站首页、热点数据等)预渲染为静态页面,缓存至 CDN,保证最佳的访问性能
-
非关键性的页面(如流量很少的老旧内容)先响应 fallback 内容,然后浏览器渲染(CSR)为实际数据;同时对页面进行异步预渲染,之后缓存至 CDN,提升后续用户访问的性能
页面的更新始终返回 CDN 的缓存数据(无论是否过期);如果数据已经过期,那么触发异步的预渲染,异步更新 CDN 的缓存。请求页面,页面数据未过期,返回预渲染页面;页面数据过期,拉取最新数据,重新预渲染。
优点:
1、具有 SSG 的所有优点,并且它减少了应用程序的构建和部署时间,因为它避免了在构建期间预渲染所有页面
2、如果数据有任何更新,则重新生成页面,而无需重建整个应用程序
3、提高了网站的更新效率
缺点:
1、对于没有预渲染的页面,用户首次访问将会看到一个 fallback 页面,此时服务端才开始渲染页面,直到渲染完毕。这就导致用户体验上的不一致
2、对于已经被预渲染的页面,用户直接从 CDN 加载,但这些页面可能是已经过期的,甚至过期很久的,只有在用户刷新一次,第二次访问之后,才能看到新的数据。对于电商这样的场景而言,是不可接受的(比如商品已经卖完了,但用户看到的过期数据上显示还有)
3、实现起来可能较为复杂,需要额外的配置和管理
DPR分布式持久渲染
DPR 是一种利用分布式计算资源进行持续渲染的技术。它可能涉及在多个节点上并行处理渲染任务,以提高渲染效率和可扩展性。然而,DPR 并不是一个广泛认可或标准化的前端渲染技术术语,它可能代表了某种特定技术或方案的概念。
DPR 本质上讲,是对 ISR 的模型做了几处改动,并且搭配上 CDN 的能力:
1、去除了 fallback
行为,而是直接用 On-demand Builder
(按需构建器)来响应未经过预渲染的页面,然后将结果缓存至 CDN
2、数据页面过期时,不再响应过期的缓存页面,而是 CDN
回源到 Builder
上,渲染出最新的数据
3、每次发布新版本时,自动清除 CDN
的缓存数据
优点:
1、提高渲染效率和可扩展性。
2、利用分布式资源优化渲染性能。
缺点:
1、新页面访问可能会触发 On-demand Builder 同步渲染,导致当次请求响应时间比较长;
2、DoS 攻击比较难防御,因为攻击者可能会大量访问新页面,导致 Builder 被大量并行运行,这里需要平台方实现 Builder 的归一化和串行运行
混合渲染模式
混合渲染模式(Hybrid Rendering Modes)是现代Web开发中非常流行且有效的策略,旨在结合不同渲染技术的优点以优化网站性能和用户体验。
SSR + CSR
SSR 似乎已经解决了 CSR 带来的问题,但 CSR 也有自己的优势,比如使用 CSR 时,页面切换无需刷新,无需重新请求整个 HTML 的内容。因此,我们可以各取所长,各补其短,于是就有了 SSR + CSR 的方案:
-
首次加载页面走 SSR:保证首屏加载速度的同时,并且满足 SEO 的诉求
-
页面切换走 CSR:Next.js 会发起一次网络请求,执行
getServerSideProps
函数,拿到它返回的数据后,进行页面渲染二者的有机结合,可以大大减少后端服务器的压力和成本,还能提高页面切换的速度,进一步提升用户的体验。
优势:
1、首屏加载速度快:通过SSR生成初始的HTML,可以确保首屏内容迅速展示给用户,这对SEO和用户体验都至关重要。
2、后续页面切换流畅:页面切换时,CSR接管页面渲染,通过JavaScript动态更新页面内容,避免了整页刷新,提升了用户体验。
3、减轻服务器压力:非首次访问的页面切换主要由客户端处理,减少了服务器的渲染负担。
适用于需要快速首屏加载且后续页面交互频繁的Web应用,如电商平台、新闻网站等。
SSG + CSR
SSR 需要较高的服务器运维成本。对于某些静态网站或者实时性要求较低的网站来说,是没有必要使用 SSR 的。那么如果用 SSG 代替 SSR,使用 SSG + CSR 的方案,是不是会更好:
-
静态内容走 SSG:对于页面中较为静态的内容,比如导航栏、布局等,可以在编译构建时预先渲染静态 HTML
-
动态内容走 CSR:一般会在 useEffect 中请求接口获取动态数据,然后进行页面重新渲染
虽然从体验来说,动态内容需要页面重新渲染后才能出现,体验上没有 SSR 好,但是避免 SSR 带来的高额服务器成本的同时,也能保证首屏渲染时间不会太长,相比纯 CSR 来说,还是提升了用户体验。
优势:
1、低成本高效能:SSG预先生成静态HTML,部署到CDN后,可以高效分发内容,降低了服务器成本。
2、动态内容灵活更新:CSR允许页面在客户端根据用户交互动态加载数据,保持了应用的灵活性和响应性。
3、SEO友好:由于初始页面是静态的,因此仍然对SEO友好。
适用于内容相对静态但偶尔需要动态更新的网站,如博客、文档站点等。
SSG + SSR
在上面介绍的 ISR 方案时提及过,ISR 的实质是 SSG + SSR:
-
静态内容走 SSG:编译构建时把相对静态的页面预先渲染生成 HTML,浏览器请求时直接返回静态 HTML
-
动态内容走 SSR:浏览器请求未预先渲染的页面,在运行时通过 SSR 渲染生成页面,然后返回到浏览器,并缓存静态 HTML,下次命中缓存时直接返回
ISR 相比于 SSG + CSR 来说,动态内容可以直接直出,进一步提升了首次访问页面时的体验;相比于 SSR + CSR 来说,减少没必要的静态页面渲染,节省了一部分后端服务器成本。
优势:
1、结合SSG和SSR的优点:静态内容通过SSG预先渲染,提高加载速度和SEO;动态内容通过SSR在请求时生成,保证内容的新鲜度。
2、智能缓存管理:ISR允许根据内容更新的频率智能地缓存和重新生成页面,减少了不必要的渲染工作。
3、成本效益:相比纯SSR,ISR减少了不必要的渲染操作,从而降低了服务器成本。
适用于需要频繁更新部分页面内容但又希望保持高性能和SEO的网站,如社交媒体、新闻聚合网站等。
同构
并不是所有的 WEB 应用都必须使用 SSR,需要权衡利弊,因为服务端渲染会带来以下问题:
-
代码复杂度增加。为了实现服务端渲染,应用代码中需要兼容服务端和客户端两种运行情况,而一部分依赖的外部扩展库却只能在客户端运行,需要对其进行特殊处理,才能在服务器渲染应用程序中运行。
-
需要更多的服务器负载均衡。由于服务器增加了渲染HTML的需求,使得原本只需要输出静态资源文件的 node 服务,新增了数据获取的 IO 和渲染 HTML 的 CPU 占用,如果流量突然暴增,有可能导致服务器down机,因此需要使用响应的缓存策略和准备相应的服务器负载。
-
涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
假如我们需要在项目中使用服务端渲染,我们需要做什么呢?那就是同构我们的项目。
基于 CSR 和 SSR 各自的优缺点,如果可以将它们进行结合,那么就可以实现互补,而这也就是同构渲染需要做的事,其中的同构就是指应用代码的主体可以同时运行在服务端和客户端。
同构定义
同构(Isomorphic)或称为通用(Universal)渲染,是Web开发中一种重要的模式,特别是在使用现代JavaScript框架(如React, Vue.js等)时。它允许开发者编写一套代码,这些代码既可以在服务器上执行,用于生成初始的HTML,也可以在客户端执行,以支持后续的交互和页面更新。这种模式的实现需要解决几个关键问题,包括路由、组件、数据模型的共享和兼容性问题。
在服务端渲染中,有两种页面渲染的方式:
-
前端服务器通过请求后端服务器获取数据并组装HTML返回给浏览器,浏览器直接解析HTML后渲染页面
-
浏览器在交互过程中,请求新的数据并动态更新渲染页面
这两种渲染方式有一个不同点就是,一个是在服务端中组装html的,一个是在客户端中组装html的,运行环境是不一样的。所谓同构,就是让一份代码,既可以在服务端中执行,也可以在客户端中执行,并且执行的效果都是一样的,都是完成这个html的组装,正确的显示页面。也就是说,一份代码,既可以客户端渲染,也可以服务端渲染。
同构渲染意味着使用 Node.js 和 JavaScript,因为它们允许重用库,并使浏览 JavaScript 代码能够在 Node.js 环境中运行,而无需进行太多修改。构建同构应用的最终目的是从一份项目源码中构建出 2 份 JavaScript 代码。一份用于在Node.js环境中快速渲染出HTML页面,提高首屏加载速度和SEO性能;另一份用于在浏览器中运行,实现页面的动态交互、数据绑定和路由跳转等功能,提供丰富的用户体验。
优势:
1、首屏加载速度快:由于服务器已经预先渲染了HTML,用户首次访问页面时可以立即看到内容,而无需等待JavaScript执行和DOM构建。
2、SEO友好:搜索引擎爬虫可以直接解析由服务器返回的HTML,使得内容更容易被索引。
3、客户端体验流畅:一旦初始页面加载完成,客户端的JavaScript接管页面,提供快速的交互和页面更新。
同构条件
为了实现同构,我们需要满足什么条件呢?首先,我们思考一个应用中一个页面的组成,假如我们使用的是Vue.js,当我们打开一个页面时,首先是打开这个页面的URL,这个URL,可以通过应用的路由匹配,找到具体的页面,不同的页面有不同的视图,那么,视图是什么?从应用的角度来看,视图 = 模板 + 数据,那么在 Vue.js 中, 模板可以理解成组件,数据可以理解为数据模型,即响应式数据。所以,对于同构应用来说,我们必须实现客户端与服务端的路由、模型组件、数据模型的共享。
1、路由共享:
-
客户端和服务端需要共享路由配置,以便在请求到达时,服务端能够找到对应的组件进行渲染。
-
通常使用框架提供的路由库(如Vue Router, React Router)配合服务端渲染中间件(如Next.js, Nuxt.js)来实现。
2、组件共享:
-
组件需要在客户端和服务端都能正确运行。
-
需要注意避免在组件中使用仅客户端可用的API(如window对象)。
-
使用条件渲染或环境变量来区分服务端和客户端的特定代码。
3、数据模型共享:
-
数据模型(状态管理)需要在客户端和服务端之间保持同步。
-
可以通过在服务端预取数据并在客户端初始化状态树来实现。
-
使用如Redux, Vuex等状态管理库可以更方便地在客户端和服务端之间共享状态。
4、环境兼容性:
-
编写兼容服务端和客户端的代码,处理全局对象(如window, document)的缺失。
-
使用Webpack或类似工具进行代码分割和打包,确保服务端仅包含必要的代码。
5、构建和部署:
-
需要配置适当的构建工具和服务器环境,以支持服务端渲染。
-
确保应用能够在Node.js环境中运行,并正确处理路由和数据请求。
同构流程
服务端渲染应用快照
在服务端,Vue 组件会被渲染为静态的 HTML 字符串,然后发送给客户端浏览器,服务端生成的 HTML
内容是在当前数据状态下应用的快照:
-
生成应用快照的同时,还会生成当前数据状态的 初始数据,用于提供给客户端做初始化处理
-
应用快照不具备事件绑定能力,即定义好的事件不会被注册到对应的
DOM
上 -
应用快照不具备数据响应式的能力,即不具备和用户进行数据交互的能力,不会执行
beforeUpdate、updated
生命周期 -
应用快照不具备节点挂载的能力,即不需要在服务端运行时进行节点挂载操作,不会执行
beforeMount、mounted
生命周期钩子 -
应用快照不具备组件销毁的能力,即不会执行组件的
beforeUnMount、unMount
生命周期钩子
服务端渲染时不提供上述的功能是因为在服务端渲染根本不需要关注这些,另外也是为了使服务端的渲染压力更小,关注更少的内容。
客户端激活
同构应用运行原理的核心在于虚拟 DOM
在浏览器端,需要渲染这段从服务端返回的 HTML 内容,即此时页面中已经存在 组件对应的 DOM 元素,除此之外该组件还会被打包到一个 JavaScript 文件中,并在客户端被 下载、解析、执行,也就是进入 客户端激活,后续页面内容的渲染都不需要服务器进行处理动态编译处理。
客户端的 JavaScript
脚本处理核心内容:
-
将当前页面已渲染的 DOM 元素与 Vue.js 所渲染的虚拟 DOM 之间建立联系
-
由于 真实
DOM
和 虚拟DOM
对象都是树形结构,并且节点间存在相互对应关系,激活 就可以通过递归地在 真实DOM
和 虚拟DOM
之间建立联系,即vnode.el = el
,并保证是从容器元素的第一个子节点开始,即el.firstChild
-
-
为页面中的
DOM
元素添加事件绑定,使得页面本身支持事件交互 -
Vue.js
从HTML
页面中提取由服务端序列化后发送过来的数据,用于初始化整个Vue.js
的应用程序
如何实现同构渲染
1、使用Vue、React等框架的官方解决方案。优点是有助于理解原理,缺点是需要搭建环境,比较麻烦
① 服务端 要渲染 Vue 组件 意味着需要处理
*.vue
、*.css
、*.ts
等依赖模块,而这些是 node 本身就不能处理的内容,也不是renderToString
能够处理的,因此需要借助 打包构建工具(如 webpack) 进行处理。② 客户端 实际也需要一个独立的客户端构建版本,虽然最新版本的 Node.js 完全支持 ES2015 特性,但对于旧的浏览器仍然需要对代码进行转译、兼容处理。
③ 基本思路,使用 webpack 同时打包客户端和服务端应用,其中服务端的包会被引入到服务端用来渲染 HTML,同时客户端的包会被送到浏览器用于 激活静态标记。
④ 与之对应的两个入口文件就是:
entry-client.js
和entry-server.js
(更多具体的配置可参见 官方文档)
2、使用第三方解决方案,比如:React生态的Next.js和Vue生态的Nuxt.js
参考资料:
【万字长文警告】从头到尾彻底理解服务端渲染SSR原理 - 掘金 (juejin.cn)
新一代Web技术栈的演进:SSR/SSG/ISR/DPR都在做什么? (qq.com)
Vuejs设计与实现 —— 同构渲染 - 掘金 (juejin.cn)