研究ssr距现在有些时日了,本文将记录总结我当时踩过的和看到的坑,为同样在研究ssr的小伙伴节省时间成本,欢迎大佬指点补充

常规三问(是什么为什么怎么做)

懒得听解释的跳过本段直奔主题

ssr是什么

最难抽中的顶级式神。。。(误)捂脸

简单来讲,ssr是指vue中的服务端渲染

默认vue开发打包生成是普通的客户端渲染,将vue打包成js然后在html中引入进而渲染DOM和操作DOM。而服务端渲染是将vue渲染成html后再发送至客户端,中间会有一系列匹配工作

为什么用ssr

截两张图你就明白了

默认方式源代码:
ssr
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
2
3
4
5
6
7
8
//https://github.com/vuejs/vue-hackernews-2.0/issues/52#issuecomment-255594303
const { JSDOM } = require('jsdom')
const dom = new JSDOM('<!doctype html><html><body></body></html>',
{ url: 'http://localhost' })

global.window = dom.window
global.document = window.document
global.navigator = window.navigator

router中配置了scrollBehavior,客户端正常,服务端报错scroll undefined

跟上个问题相同,需要在服务端重声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//fixed Not-implemented error
const isServer = process.env.VUE_ENV === 'server'

if(isServer) {
window.scrollTo = function(x, y) {
// do something or not
}
}

export function createRouter() {
return new Router({
scrollBehavior: () => ({ y: 0 }),
routes: [
{ path: '/', component: Homepage }
]
})
}

mismatch

使用ssr会有检查服务端渲染出的结构与直接客户端渲染的结构是否相同,不同会报mismatch。这种问题往往是因为比如table结构没有tbody之类的。

自己的一些业务操作也可能会产生两端的结构重复,比如我之前为了动态生成meta用了mixin,在服务端用$ssrContext配合操作,客户端则用的document直接更改对应值,因此会出现一个页面有两个重复的meta,造成mismatch,解决方式是在客户端加判断,如果已经有的meta就使用修改而不是增加

1
2
3
4
5
6
if (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
9
Vue.mixin({
beforeRouteEnter(to, from, next) {
if(judgeUserAgent() && to.path === '/a/' ) {
next('/b/')
} else {
next()
}
}
})

服务端: 在server.jsrender中通过req.headers['user-agent']然后通过$ssrContext传递

1
2
3
4
5
if(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
    5
    clientConfig.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.jsoutput

    1
    2
    3
    4
    5
    output: {
    path: path.resolve(__dirname, '../dist'),
    publicPath: '/dev/dist/',
    filename: '[name].[chunkhash].js'
    },
  • entry-client.jsservice worker

    1
    2
    3
    if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    navigator.serviceWorker.register('/dev/service-worker.js')
    }
  • template.htmlhref

    1
    <link rel="shortcut icon" href="/dev/assets/images/favicon.ico">
  • server.jsserve

    1
    2
    3
    4
    app.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.jsassetsPublicPath/dev/