react-ssr
为什么使用SSR
与传统 SPA(Single-Page Application - 单页应用程序)相比
服务器端渲染(SSR)的优势主要在于:
-
更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
请注意,截至目前,Google 和 Bing 可以很好对同步 JavaScript 应用程序进行索引。在这里,同步是关键。如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再行抓取页面内容。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染(SSR)解决此问题。
-
更快的内容到达时间(time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。
无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。通常可以产生更好的用户体验,并且对于那些「内容到达时间(time-to-content)与转化率直接相关」的应用程序而言,服务器端渲染(SSR)至关重要。
使用服务器端渲染(SSR)时还需要有一些权衡之处:
-
开发条件所限。
浏览器特定的代码,只能在某些生命周期钩子函数(lifecycle hook)中使用;一些外部扩展库(external library)可能需要特殊处理,才能在服务器渲染应用程序中运行。
-
涉及构建设置和部署的更多要求。
与可以部署在任何静态文件服务器上的完全静态单页面应用程序(SPA)不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
-
更多的服务器端负载。
在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源(CPU-intensive - CPU 密集),因此如果你预料在高流量环境(high traffic)下使用,请准备相应的服务器负载,并明智地采用缓存策略。
具体流程
1、webpack打包生成bundle.js并注入生成的html模板
webpack会从入口index.js将内容打包为一个bundle.js文件然后注入到html模板中,通常将该模板放在app的view中以供node渲染。
2、babel将src文件夹中的内容转换为node能识别的内容
因为src中的jsx所用的import和export等node不能识别,需要被转换成required。被转换后的内容放在dist文件夹中。
3、用户访问某路径时将该页面renderToString后的结果注入进相关html模板
// www.test.com/show await this.ctx.renderSSR('show.html', { App, rootReducer, initState, useRem: true });
-
node读取dist中show的app.js(之前是app.jsx)
-
node获取后端数据
-
将后端数据作为初始数据传入reducer并生成store,通过Provie传入<App />
-
将<App />通过renderToString生成字符串注入html模板
-
将拿到的初始数据作为window._init_data传到前端
<div id='app'>{{ stringData || '' }}</div> <script>window._initData={{ initData || '' }}</script> <script src="bundle.js"></script>
-
将该模板响应给浏览器
4、前端render
浏览器拿到html开始从头解析,将之前的字符串模板渲染成DOM。执行到最后有一个bundle.js然后进入src的index.js执行ReactDOM.render,render的时候会采用diff算法和之前渲染成功的DOM做比对。
前端用全局的_init_data来作为初始化数据传入reducer生成客户端store,之后的action都在前端做,和后端的redux没关系了。
总结
如果是spa的话那么是这样的:
<div id="app"></div> <script src="bundle.js"></script>
我们的页面是不支持SEO的,因为该页面被响应到了浏览器之后再由浏览器解析js动态生成页面。为了支持SEO,我们应该在服务器端通过renderToString方法将页面的主屏内容生成字符串插入id为app的div里。将页面的初始数据通过全局变量传入,类似这样:
<div id='app'>{{ stringData || '' }}</div> <script>window._initData={{ initData || '' }}</script> <script src="bundle.js"></script>
通过这样的方式可以实现SEO,但是仅仅是支持首屏SEO。如果我们有前端路由跳转,那么路由跳转后的页面时不能被SEO的,所以我们要这样:
// node app.get('/index|/login|/rules', controller.index.rend);
当用户通过h5方式做跳转时,每次访问的url都返回同一个html,可以达到每一个路由的SEO。