为什么要使用预渲染?

为了应付SEO(国内特别是百度)考虑在网站(vue技术栈系列)做一些优化。大概有几种方案可以考虑:

服务端做优化:

第一,ssr,vue官方文档给出的服务器渲染方案,这是一套完整的构建vue服务端渲染应用的指南,具体参考https://cn.vuejs.org/v2/guide/ssr.html

第二,nuxt 简单易用,参考网站 https://zh.nuxtjs.org/guide/installation

 
前端做优化:
第三,vue-meta-info + prerender-spa-plugin做预渲染,这个是针对单页面的meta SEO的另一种思路,参考网站 https://zhuanlan.zhihu.com/p/29148760
第四,phantomjs 页面预渲染,具体参考 phantomjs.org (已经暂停维护了)
甚至我一度考虑过第五种方案来应付百度:做假html节点(节点最终不展示出来)。
 
权衡了一下,做服务端渲染是没有人力物力了,所以选用了预渲染的方式来处理(第三种),其中遇到几个大坑,记录一下。

 

1. 下载/安装失败

这个问题有网友遇到的比我多,直接引用解决方案:https://blog.csdn.net/wangshu696/article/details/81253124

基本上是使用cnpm/高版本node都能解决掉。

 

2. 最大的一个坑:CDN支持。

网络上有解决方案,这篇文章写的比较清楚:https://juejin.im/post/5cc5af1f6fb9a032447f0299

在github上也有对应的问题,解决方案主要是上面链接中的第三种。https://github.com/chrisvfritz/prerender-spa-plugin/issues/114,里面提供的demo也差不多:https://github.com/Dhgan/prerender-cdn-demo【注:这个例子实际有一个问题,预渲染处理html替换是匹配时会多出一个“/”,比如“https://www.cdn.com//test.img”,正则需要改一下,可以看我下面的例子】

重点在于理解预渲染的原理:在webpack打包结束并生成文件后(after-emit hook),启动一个server模拟网站的运行,用puppeteer(google官方的headless chrome浏览器)访问指定的页面route,得到相应的html结构,并将结果输出到指定目录,过程类似于爬虫。

所以CDN配置预渲染失败原因很简单:在启用puppeteer爬虫时,你的资源在CDN上根本就没有(其他诸如图片资源还好说,但是js资源都没有,咋渲染啊)。
 

我的方案(掘金文章描述的第三种方案:利用webpack的全局变量和正则替换):

网络上方案是提供了,但是貌似细节都不是很全面,这里本人全面的讲述一下。
原理:在webpack打包时使用和本地环境一样的配置,保证puppeteer爬虫时成功,然后分成两步一起来来加上CDN:
  • 第一步,对于生成的html文件,使用正则方式将资源的引用路径替换为CDN引用;
  • 第二步,对于解析js时才发起的资源请求,给webpack运行时暴露的全局变量__webpack_public_path__设置publicPath,相关文档,可以用于项目运行时动态加载的js/css修改成cdn域名。
 
第一步处理:
有两个注意事项:
1. 预渲染中output的publicPath需要和预渲染中处理html的正则配对使用。比如网上的例子基本都使用默认值:空字符''(或者不设置)。一旦设定了非空字符的值,预渲染的html匹配要对应修改。网上的例子为:红色字体部分要配对使用,
//webpack.common.js
{
output: {
filename: '[name].js',
path: config.outPath,
// 需要注意,预渲染的publicPath要和PrerenderSPAPlugin中的匹配规则对应
publicPath: '' // 设置成默认值或者不设置也可以
}
}


// webpack.prod.js
{
plugins: [
new PrerenderSPAPlugin({
staticDir: config.build.assetsRoot,
routes: [ '/', '/about', '/contact' ],
postProcess (renderedRoute) {
// add CDN
renderedRoute.html = renderedRoute.html.replace(
/(<script[^<>]*src=\")((?!http|https)[^<>\"]*)(\"[^<>]*>[^<>]*<\/script>)/ig,
`$1${config.build.cdnPath}$2$3`
).replace(
/(<link[^<>]*href=\")((?!http|https)[^<>\"]*)(\"[^<>]*>)/ig,
`$1${config.build.cdnPath}$2$3`
)

return renderedRoute
},

renderer: new Renderer({
injectProperty: '__PRERENDER_INJECTED__',
inject: 'prerender'
})
})

]
}

 本人的实际运用比上面要复杂一些,publicPath保留了之前项目的值"/",对应的匹配也就要更改。而且添加了对img标签/内联图片以及部分项目特有的处理。

publicPath要以"/"结尾的,相关文档,所以cdnPath要以“/”结尾

prerender-spa-plugin预渲染踩坑
// webpack.common.js
{
    output: {
        filename: '[name].js', 
        path: config.outPath,
        // 需要注意,预渲染的publicPath要和PrerenderSPAPlugin中的匹配规则对应
        publicPath: '/'
    }
}



// webpack.prod.js
webpackConfig.plugins.push(new PrerenderSPAPlugin({
    // Required - The path to the webpack-outputted app to prerender.
    staticDir: config.outPath,
    // indexPath: path.join(config.outPath, 'index.html'),
    // Required - Routes to render.
    routes: [ '/', '/course', '/to-class', '/declare', '/agreement', '/user'],
    postProcess (renderedRoute) {
        // add CDN
        // 由于CDN是以"/"结尾的,所以资源开头的“/”去掉
        renderedRoute.html = renderedRoute.html.replace(
            /(<script[^<>]*src=\")(?!http|https|\/{2})\/([^<>\"]*)(\"[^<>]*>[^<>]*<\/script>)/ig,
            `$1${config[env].assetsPublicPath}$2$3`
        ).replace(
            /(<link[^<>]*href=\")(?!http|https|\/{2})\/([^<>\"]*)(\"[^<>]*>)/ig,
            `$1${config[env].assetsPublicPath}$2$3`
        ).replace(/(<img[^<>]*src=\")(?!http|https|data:image|\/{2})\/([^<>\"]*)(\"[^<>]*>)/ig,
            `$1${config[env].assetsPublicPath}$2$3`
        ).replace(/(:url\()(?!http|https|data:image|\/{2})\/([^\)]*)(\))/ig,// 样式内联,格式必须是":url(/xxx)",其他格式都不行【用来剔除js代码中类似的字段】
                `$1${config[env].assetsPublicPath}$2$3`
        ).replace(/(<div class="dialog_mask_\w+">)[\s\S]*<\/div>(<\/body>)/ig, `$2`)// 去掉警告弹窗(因为部分调用比较早的ajax会报错导致多出了弹出框)

        return renderedRoute
    },
    renderer: new Renderer({
        injectProperty: '__PRERENDER_INJECTED__',
        inject: 'prerender',
        renderAfterDocumentEvent: 'render-event'
    })
}));
View Code

 publicPath和postProcess配对使用的,postProcess中的匹配有小改动,目的是为了剔除重复的"/"。其中config[env].assetsPublicPath是本人的CDN路径变量。

相关文章:

  • 2022-12-23
  • 2021-10-23
  • 2022-12-23
  • 2022-12-23
  • 2021-09-18
  • 2021-11-11
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-11-12
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-11-08
相关资源
相似解决方案