6

seo-idea-lightbulbs-ss-1920的副本.jpg

一、介绍

单应用框架的项目,打包出来的文件中只有一个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字样的名称,也容易被识别


石坚
413 声望14 粉丝