vue服务端渲染(SSR)踩坑集锦
研究ssr距现在有些时日了,本文将记录总结我当时踩过的和看到的坑,为同样在研究ssr的小伙伴节省时间成本,欢迎大佬指点补充
常规三问(是什么为什么怎么做)
懒得听解释的跳过本段直奔主题
ssr是什么
最难抽中的顶级式神。。。(误)
简单来讲,ssr是指vue中的服务端渲染
默认vue开发打包生成是普通的客户端渲染,将vue打包成js然后在html中引入进而渲染DOM和操作DOM。而服务端渲染是将vue渲染成html后再发送至客户端,中间会有一系列匹配工作
为什么用ssr
截两张图你就明白了
默认方式源代码:
ssr方式源代码:
也就是说使用默认方式,当在搜索引擎搜索你的站点关键词时可能就搜不到你的站点,这对一些需要被erveryone熟知的站点是致命的。
除此之外服务端渲染也能更好的解决浏览器兼容的问题,并且在性能上也帮客户端做了很多事情
如何开发ssr项目
两种方式,自己搭建或者使用官方框架Nuxt.js,我当时是自己搭的,参考了官方示例vue-hackernews-2.0,基本方式都是相同的,打包出对应route的bundle,与template合并,生成html string,展示
直奔主题
使用服务端渲染常见问题:
window/alert/document is undefined
服务端没有
window/alert/document
这种东西,需要自行定义,建议方式引入第三方包jsdom
辅助定义1 | //https://github.com/vuejs/vue-hackernews-2.0/issues/52#issuecomment-255594303 |
scrollBehavior
,客户端正常,服务端报错scroll undefined
跟上个问题相同,需要在服务端重声明
1 | //fixed Not-implemented error |
使用ssr会有检查服务端渲染出的结构与直接客户端渲染的结构是否相同,不同会报
mismatch
。这种问题往往是因为比如table
结构没有tbody
之类的。
自己的一些业务操作也可能会产生两端的结构重复,比如我之前为了动态生成meta
用了mixin,在服务端用$ssrContext
配合操作,客户端则用的document
直接更改对应值,因此会出现一个页面有两个重复的meta
,造成mismatch
,解决方式是在客户端加判断,如果已经有的meta
就使用修改而不是增加1
2
3
4
5
6if (meta) {
$.parseHTML(meta).forEach(function(el) {
$('meta[name=' + $(el).attr('name') + ']')
.attr('content', $(el).attr('content'))
})
}
比如在PC端使用a链接作为入口,移动端使用b链接作为入口
客户端:使用navigator.userAgent
做判断,然后1
2
3
4
5
6
7
8
9Vue.mixin({
beforeRouteEnter(to, from, next) {
if(judgeUserAgent() && to.path === '/a/' ) {
next('/b/')
} else {
next()
}
}
})
服务端: 在server.js
的render
中通过req.headers['user-agent']
然后通过$ssrContext
传递1
2
3
4
5if(context.agentID !== null && context.url === '/a/') {
router.push('/b/')
} else {
router.push(context.url)
}
一般打包都打包到根目录,获取静态文件资源也从/开始,如果不是,怎么办呢?
其实也不难,把各种相关配置更改一下就好了,就是这些位置自己摸索到时候有些麻烦,尤其还是ssr,漏掉就可能造成项目起不来或白屏、报错、刷新404。
我这里列了一下要修改的位置(按文件顺序,假设二级目录名为dev):
build/setup-dev-server.js
中的webpack-hot-middleware
1
2
3
4
5clientConfig.entry.app = ['webpack-hot-middleware/client?path=/dec/__webpack_hmr', clientConfig.entry.app]
app.use(require('webpack-hot-middleware')(clientCompiler, {
path: '/dev/__webpack_hmr'
}))webpack.base.config.js
的output
1
2
3
4
5output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dev/dist/',
filename: '[name].[chunkhash].js'
},entry-client.js
的service worker
1
2
3if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/dev/service-worker.js')
}template.html
的href
1
<link rel="shortcut icon" href="/dev/assets/images/favicon.ico">
server.js
的serve
1
2
3
4app.use(favicon('./src/assets/images/favicon.ico'))
app.use('/dev/dist', serve('./dist', true))
app.use('/dev/assets', serve('./src/assets', true))
app.use('/dev/service-worker.js', serve('./dist/service-worker.js'))
如果是非服务端渲染需要修改config/index.js
中assetsPublicPath
为/dev/