涉及到的技术
本方式基于webpack、react、redux、react-router、koa、axios等技术封装,本方案未完待续~
简介及特点
更利于SEO。
不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本(Google除外,据说Googlebot可以运行javaScript)。使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少(如图一)。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。更利于首屏渲染
首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。
局限
服务端压力较大
本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源;开发条件受限
在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制;学习成本相对较高
除了对webpack、React要熟悉,还需要掌握node、Koa2等相关技术。相对于客户端渲染,项目构建、部署过程更加复杂
package.json
1 | { |
基于webpack构建
1 | // webpack.client.js |
store
getClientStore
需要把第一次服务端渲染的state同步给client的store1
return createStore(reducers, window.__GLOBAL_INIT_STATE, applyMiddleware(thunk, logger));
getServerStore
containers(共享)
- react组件,前后端共享
- 未按redux方式组织目录,采用了
以应用的状态作为模块的划分依据
- 目录结构采用了Ducks: Redux Reducer Bundles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15➜ ssr git:(master) ✗ tree src/containers
src/containers
├── App
│ ├── action.js
│ ├── index.js
│ ├── index.scss
│ ├── reducer.js
│ └── type.js
├── Main
│ └── index.js
├── Search
│ ├── index.css
│ ├── index.js
│ └── reducer.js
└── reducers.js
client
和server端共用一套路由,通过renderRoutes渲染路由1
2
3
4
5
6
7
8
9
10
11import React from 'react';
import ReactDOM from 'react-dom';
import routes from '../routes';
import { BrowserRouter, Route } from 'react-router-dom';
import {renderRoutes,matchRoutes} from 'react-router-config'
import { Provider } from 'react-redux';
import { getClientStore } from '../store';
ReactDOM.hydrate(<Provider store={getClientStore()}>
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter></Provider>, document.getElementById('root'));
services(共享)
client/server都会调用该层,故抽到单独一层
- 基于axiox封装了request方法
- 服务端调接口时透传了cookie
- NetInterface时封装了常用业务接口
routes(共享)
1 | import React, { Fragment } from 'react'; |
server
- koa承担server服务
- react-router-config的matchRoutes来做路由匹配及渲染
- 匹配成功的路由会调组件外挂getInitialProps静态方法
- 通过
koa-proxy
进行了部分接口转发,避免了跨域问题1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41import React from 'react';
import { StaticRouter} from 'react-router-dom';
import {renderRoutes,matchRoutes} from 'react-router-config'//渲染路由
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import routes from '../routes';
import { getServerStore } from '../store';
export default async function (reqPath) {
const context = {
name: 'xutao'
};
let store = getServerStore();
let matchedRoutes= matchRoutes(routes,reqPath);
let promises = [];
matchedRoutes.forEach(item=>{
if(item.route.getInitialProps){
promises.push(item.route.getInitialProps(store));
}
});
await Promise.all(promises);
const html = renderToString(
<Provider store={store}>
<StaticRouter context={context} location={reqPath}>{renderRoutes(routes)}</StaticRouter>
</Provider>
);
const htmlTemplate = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root">${html}</div>
<script>window.__GLOBAL_INIT_STATE = ${JSON.stringify(store.getState())}</script>
<script src="/static/client.js"></script>
</body>
</html>`;
return htmlTemplate;
}