一、介绍
单应用框架的项目,打包出来的文件中只有一个index.html作为入口,根据页面的路由加载对应页面的js。
当项目需要支持SEO时,在条件允许的情况下可以选择node作为同构方案,配合react-router-config匹配routes中的components,再通过rendersToString方法打印出内容,注入到html模板中。
此方案的优点是可以支持动态路由、后端动态获取数据。缺点是每次访问都要生成一次、引进了node服务。
如果在网站被访问之前就把html预编译好,多个路由对应多个html,这就是下面要介绍的预编译方案。
在umi项目中配置预编译非常简单
umirc.js
plugins: [['@umijs/plugin-prerender']],
ssr: true,
二、pre-render
pre-render插件在umi打包结束后,按照routes里的url一个个访问ssr,类似于构造虚拟访问来生成html。
routes除非指定了黑白名单,否则将生成所有html
umirc.js
[
'@umijs/plugin-prerender',
{
include: ['/'],
exclude: ['/help']
},
]
三、ssr
把umirc.js中ssr设置成true后,dist文件夹下会多生成了一个umi.server.js文件,它是以/src/pages/.umi文件为入口、囊括了其他所有dist/*.js生成的大文件,并且经过了环境变量上的处理。
1.区分browser打包
- __IS_BROWSER
/src/pages/.umi是每次打包后都会生成的中间结果,查看代码会发现它是以__IS_BROWSER变量区分浏览器、node环境,而umi.server.js则是把__IS_BROWSER变量替换成boolean的结果,因此在业务代码中,可以直接使用__IS_BROWSER这一变量区分你的逻辑。
例如,页面中一开始有loading等待缓冲的效果,可以使用__IS_BROWSER将loading取消,避免编译出来的内容无效。
- runInMockContext
上面提到的loading效果的区分,也可以通过在配置中自定义环境变量
[
'@umijs/plugin-prerender',
{
runInMockContext: {
__isCustomEnv: true
}
}
]
2.mockWindow
umi-server在调用umi.server.js时会给global变量附加mock过的window对象,例如,在业务代码中访问window.location.url时,获取到的是浏览器环境一样的结果。
3.getInitialProps
在预编译之前,想要给html中放入一些内容,但这些内容又不需要在实际业务中展示。这时可以选择给routes下定义的component添加getInitialProps方法
(1)静态内容
function Index(props){
return props.nodeContent || 'hello world'
}
Index.getInitialProps = ()=>({
nodeContent: '我特地为seo增加的内容'
})
export defalut Index
将不经常更新的内容放到getInitialProps中写死,生成的内容供seo抓取,不过在js执行后会将其刷新掉。
(2)动态内容
function Index(props){
return props.nodeContent || 'hello world'
}
Index.getInitialProps = ()=>{
return API.getData(); //promise ==> {nodeContent:'动态内容'}
}
export defalut Index
返回一个promise也能传递到组件的props中,这个适合于内容不固定的场景。
(3)动态路由
有些场景下需要根据路由信息来传递给后端获取动态内容
假如项目原先的动态路由配置如下
{ path: '/users/:id', component: '../pages/users' },
另外新建个文件专门对users下的所有id进行枚举
route-users-config.js
const usersRoutes = [
{ path: '/users/10', component: '../pages/users' },
{ path: '/users/11', component: '../pages/users' },
{ path: '/users/99', component: '../pages/users' },
...
];
export defalut usersRoutes
将usersRoutes追加到umi的路由配置中,再将usersRoutes的path罗列出来追加到pre-render的include中,就可生成枚举的动态路由html
动态路由的信息可以在props中获取
function Index(props){
return props.nodeContent || 'hello world'
}
Index.getInitialProps = ({location})=>{
return API.getData(location.pathname); //promise ==> {nodeContent:'动态内容'}
}
export defalut Index
4.动态title、meta
百度搜索TDK的需要,给每个页面生成不同的keywords、description、title,提高搜索引擎的收录权重。
import Helmet from 'react-helmet';
<Helmet>
<meta name="description" content={`拥有优秀的大数据分析能力,可以提供专业的用户运营和数据分析经验,提供从数据埋点采集到数据分析的全链路用户增长应用体系,支持私有化部署。`} />
<title>{`一站式数据+增长平台`}</title>
</Helmet>
[
'@umijs/plugin-prerender',
{
runInMockContext: {
__isCustomEnv: true
},
postProcessHtml: ($) => {
const helmet = Helmet.renderStatic();
const title = helmet.title.toString();
const meta = helmet.meta.toString();
const link = helmet.link.toString();
if (title !== '<title data-react-helmet="true"></title>') {
$('html head').prepend(title);
}
$('html head').append(meta)
$('html head').append(link)
return $;
}
},
],
四、拓展
1.图片
umi默认给图片加了阈值,当小于这个值时生成的图片使用base64。但在预编译时生成的html中会产生大量的base64字符,使得html变大。
这里可以设置图片的阈值,减小html的包袱
umirc.js
chainWebpack(config) {
config.module
.rule('exclude')
.use('url-loader')
.tap((options) => {
return {
...options,
limit: 1,
};
});
}
2.页面闪烁
umi使用了默认的renderToString方法,renderToString在root元素上加了data-reactroot属性,在判断内容没有变化的情况下不重新渲染。
但由于umi使用了document.ejs渲染整体html,data-reactroot属性被加在了html根元素上,所以导致第一次进入页面时会出现闪烁。
简单的解决方案是:
1.将data-reactroot属性写死在root元素上
2.在最外部加一层className,功能是设置display:none。
注意点:不能作为style直接写在元素上,会被搜索引擎识别。也不能将className设置为类似hide字样的名称,也容易被识别
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。