prerender-spa-plugin预渲染踩坑
为什么要使用预渲染?
为了应付SEO(国内特别是百度)考虑在网站(vue技术栈系列)做一些优化。大概有几种方案可以考虑:
服务端做优化:
第一,ssr,vue官方文档给出的服务器渲染方案,这是一套完整的构建vue服务端渲染应用的指南,具体参考https://cn.vuejs.org/v2/guide/ssr.html
第二,nuxt 简单易用,参考网站 https://zh.nuxtjs.org/guide/installation
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结构,并将结果输出到指定目录,过程类似于爬虫。
我的方案(掘金文章描述的第三种方案:利用webpack的全局变量和正则替换):
- 第一步,对于生成的html文件,使用正则方式将资源的引用路径替换为CDN引用;
- 第二步,对于解析js时才发起的资源请求,给webpack运行时暴露的全局变量
__webpack_public_path__
设置publicPath,相关文档,可以用于项目运行时动态加载的js/css修改成cdn域名。
//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要以“/”结尾
// 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'
})
}));
publicPath和postProcess配对使用的,postProcess中的匹配有小改动,目的是为了剔除重复的"/"。其中config[env].assetsPublicPath是本人的CDN路径变量。
第二步处理
webpackConfig.plugins.push(new PrerenderSPAPlugin({
// 。。。
renderer: new Renderer({
injectProperty: '__PRERENDER_INJECTED__',
inject: 'prerender',
renderAfterDocumentEvent: 'render-event' // vue可能需要使用预渲染何时开始的事件
})
}));
如上,注入了__PRERENDER_INJECTED__属性,值为"prerender"。
然后使用new webpack.DefinePlugin()向运行时注入变量:process.env.CDN_PATH,如:
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
CDN_PATH: JSON.stringify(config[env].assetsPublicPath)
}
}),
然后再工程的根目录下建立一个public-path.js文件,内容如下
/**
* CDN
*/
/* eslint-disable */
const isPrerender = window.__PRERENDER_INJECTED__ === 'prerender'
// 预渲染过程中使用相对路径来处理模拟浏览器爬取节点(否则会因为CDN找不到资源而卡住)
// 所以预渲染时使用'/'和publicPath一致,真正运行时值为process.env.CDN_PATH
__webpack_public_path__ = isPrerender ? '/' : process.env.CDN_PATH
注意上面红色字体部分,预渲染时使用的路径要和配置的publicPath一致。
并在app入口js引用他
import '../public-path';
import Vue from 'vue';
特别注意:使用类似mini-css-extract-plugin这样的组件将.vue的style样式提取到外部css,这会导致js中添加的__webpack_public_path__在css中不起作用,比如外链css中出现
background:url(/static/img/icon-question.f05e67f.svg) top no-repeat;
js中的CDN变量就失去作用了。需要想额外办法,解决方案有两种:
1.要么不在css中直接引用图片(在模板中插入背景url),这个用着会比较难受。
2.【推荐使用】在webpack打包时直接给所有图片资源的publicPath配置上CDN路径,图片资源在预渲染加载失败并不会导致整个预渲染失败,放心大胆使用,比如本人的
{
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
loader: 'url-loader',
exclude: [path.resolve(__dirname,'../src/assets/fonts')],
options: {
limit: 100,
name: utils.assetsPath('img/[name].[hash:7].[ext]'),
publicPath: config[env].assetsPublicPath
}
}
其他非js/css的资源(如字体文件/音频/视频文件等)类似。
额外提示: 多页面(多html入口)的项目可以调用多次预渲染插件。比如本人的项目除了index.html外,还有一个/h5/index.html为入口的大页面。这个页面本人的调用如下
// h5主页预渲染
webpackConfig.plugins.push(new PrerenderSPAPlugin({
// Required - The path to the webpack-outputted app to prerender.
staticDir: config.outPath,
// The path your rendered app should be output to.
// outputDir: path.join(config.outPath, 'h5'),
indexPath: path.join(config.outPath, 'h5/index.html'),
// Required - Routes to render.
routes: ['/h5', '/h5/about', '/h5/invite', '/h5/purchase/starter'],
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-h5-event'
})
}));
3.Vue预渲染之后报:behavior.js:149 [Vue warn]: Cannot find element: #app
4. 微信需要授权的页面的预渲染问题
这类页面不好生成预渲染页面(授权报错),建议不生成。
5. 页面加载闪现首页
部分路由是没有做预渲染的,这部分路由在nginx配置的时候往往默认指向index.html比如类似下面的配置
location / {
try_files $uri $uri/index.html /index.html;
#root /static/front; #站点目录 已经配置了全局root
}
由于对首页做了预渲染,所以index.html默认有很多内容的。
解决方案有两种:
- 默认根节点隐藏,合适时机再显式出来:https://blog.csdn.net/Christiano_Lee/article/details/94569119。(感觉思路可行,但是本人没有实践,后面实践后再加上评论)
- 新增一个空页面,路由为'/empty',并为这个路由做预渲染,nginx配置中没有匹配的路由默认指向加载此页面。nginx配置改为
location / {
try_files $uri $uri/index.html /empty/index.html; # /index.html;
#root /static/front; #站点目录 已经配置了全局root
}
prerender-spa-plugin预渲染踩坑的更多相关文章
- 什么是服务端渲染、客户端渲染、SPA、预渲染,看完这一篇就够了
服务端渲染(SSR) 简述: 又称为后端渲染,服务器端在返回html之前,在html特定的区域特定的符号里用数据填充,再给客户端,客户端只负责解析HTML. 鼠标右击点击查看源码时,页 ...
- Prerender.io - 预渲染架构,提高AngularJS SEO
近些年来,越来越多的JavaScript框架(即AngularJS,BackboneJS,ReactJS)变得越来越流行.许多公司和开发人员使用这些JavaScript框架开发应用程序.这些框架有很多 ...
- vue 预渲染遇到的坑
前言: 最近公司项目需要增加seo搜索引擎优化,到网上找了下资料,有预渲染和服务端渲染两种方式,考虑到只需要渲染首页所以我选择了先启用比较简单的预渲染方式来做seo! 步骤: 1.安装 prerend ...
- react基础学习和react服务端渲染框架next.js踩坑
说明 React作为Facebook 内部开发 Instagram 的项目中,是一个用来构建用户界面的优秀 JS 库,于 2013 年 5 月开源.作为前端的三大框架之一,React的应用可以说是非常 ...
- vue预渲染及其cdn配置
VUE SEO方案一 - 预渲染及其cdn配置 项目接入VUE这样的框架后,看起来真是太漂亮了,奈何与MCV框架比起来,单页应用程序却满足不了SEO的业务需求,首屏渲染时间也是个问题.总不能白学VUE ...
- 我的微信小程序入门踩坑之旅
前言 更好的阅读体验请:我的微信小程序入门踩坑之旅 小程序出来也有一段日子了,刚出来时也留意了一下.不过赶上生病,加上公司里也有别的事,主要是自己犯懒,就一直没做.这星期一,赶紧趁着这股热乎劲,也不是 ...
- vue项目使用 prerender-spa-plugin 预渲染
由于项目要做seo优化,而用vue写成的spa页面谷歌浏览器等是抓取不到数据的.介于ssr和预渲染来说,后者相对来说要简单许多.所以采用了预渲染方式.采用插件prerender-spa-plugin使 ...
- Vue项目预渲染机制引入实践
周末想顺便把已经做好静态页面的webApp项目做一下SEO优化,由于不想写蹩脚的SSR代码,所以准备采用预渲染,本来想着网上有这么多预渲染的文章,随便找个来跟着做不就完了嘛,结果年轻的我付出了整个周末 ...
- vue项目移植tinymce踩坑
转载:https://segmentfault.com/a/1190000012791569?utm_source=tag-newest 2019-2-18 貌似这篇文章帮了大家一些小忙最近tinym ...
随机推荐
- 搭建虚拟机+静态IP+XShell管理虚拟机+jdk+tomcat热部署
第一步:搭建虚拟机 大家可以参考http://blog.csdn.net/u012453843/article/details/68947589这篇博客进行学习 第二步:配置静态IP并且要能上网 大家 ...
- PHP三元运算符的写法
(expr1) ? (expr2) : (expr3); //php三元运算符的写法 $status = 3; $info2 = $status == 1 ? '待处理' : '已处理'; echo ...
- json属性里面出现特殊字符怎么获取属性
直接上代码. 这样的 获取这个object后需要获取里面的书属性,但是正常情况下是XX.属性名.但是属性名有特殊符号.这时候我们可以这样. XX['属性名']['属性名']....可以一直这样写 XX ...
- Test Case:: 12C ASMCMD New feature (Doc ID 1589249.1)
Test Case:: 12C ASMCMD New feature (Doc ID 1589249.1) APPLIES TO: Oracle Database - Enterprise Editi ...
- java之程序流程控制
顺序结构:代码由上至下依次执行: 分支结构: if () { } else{ } if () { } else if () { } else { } switch(常量){ case 常量: 语句; ...
- 第一个月.day1
1. 编辑器下载 推荐的是hbulider 开发环境 2. 浏览器 推荐chrome 谷歌浏览器学习 3. 建立技术笔记 推荐博客园 Web 本月任务 搭建静态网页. 静态页面:不需要网络请求 ...
- C 语言输出不同颜色字体
C 语言输出不同颜色字体 \033是8进制,它就是unix下终端转义符ESC(16进制1A,10进制27) ESC[xm 是unix下改变终端输出颜色的命令 所以,如果是红色,则我们定义为\033[0 ...
- mac怎么连接windows远程桌面
首先需要下载一个软件,因为苹果电脑并没有提供免费的软件给我们,所以不能像windows一样, 直接在任务管理中搜素远程桌面然后输入ip地址,用户名,密码就可以远程连接, 而苹果也有提供一个软件,但要付 ...
- SpringBoot控制台版图书借阅程序
// 实验存档... 效果图: 完整程序:https://pan.baidu.com/s/1-d1J90dkEtM0WKkABu0K0Q 提取码:hcnm DAO层代码由MyBatis Generat ...
- 基于STM32F429的内存管理
1.内存管理介绍 内存管理,是指软件运行时对计算机内存资源的分配和使用的技术.其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源. 内存管理的实现方法有很多种,他们其实最终都是要 ...