什么是ssr
传统vue项目
流程: 输入网址 -> 请求后端服务器 -> 返回打包资源到浏览器(js,html模板)
-> 浏览器渲染(执行js) -> 发送请求 -> 返回数据
缺点: 页面由js生成,不利于seo; 一次性获取所有js,不利于首屏渲染
ssr项目: 输入网址 -> node服务解析路由 -> node服务渲染对应页面,
生成对应页面html字符串 -> 返回页面字符串给浏览器 -> 浏览器渲染
搭建ssr+vue项目
- vue-server-renderer: 将vue实例转化为字符串
- 步骤
- 根据路由解析获取模板文件
- 读取对应页面的template和数据生成一个vue实例
- 把vue实例编译为字符串,返回给浏览器
const app = new Vue({
template: `<div></div>`,
data: {}
})
const rederer = require('vue-server-renderer').createRenderer()
renderer.renderToString(app).then(html => {
console.log(html)
})
-
结合vue实现ssr
-
每次访问必须创建一个vue实例
-
只会触发组件的beforeCreate和created钩子,所以需要客户端js
-
js拆分为服务端js,客户端js,服务端负责渲染页面和beforeCreate和created, 客户端js负责后续操作
-
需要通过webpack生成客户端和服务端js,然后在node服务器中,根据路由解析不同页面,获取对应页面client/server打包结果以及对应的template组合生成vue实例返回给浏览器
-
-
index.ssr.html
// <div id="app"></div> 删除该标签
<!-- vue-ssr-outlet --> // 该注释必须保持
<script type="text/javascript" src="<%=htmlWebpackPlugin.options.files.js=%"></script> // ejs
- router/index.js
export default function createRouter() {
return new Router({
mode: 'history',
routes: [ ... ]
})
}
- main.js
import App from './App'
const router = createRouter()
export function createApp({
const app = new Vue({
router,
render: h => h(App)
})
return { app, router }
})
- client.js
import { createApp } from './main'
const { app, router } = createApp()
router.onReady(() => {
app.$amount('#app')
})
- serve.js 服务端入口文件
import { createApp } from './main'
export default context => { // context
return new Promise((resolve, reject) => {
const { app, router } = createApp()
router.push(context.url)
router.onReady(() => {
const mathComponents = router.getMathedComponents()
if (!mathComponents) {
return resolve({ status: 404 })
}
resolve(app)
}, reject)
})
}
- webpack.server.config.js copy生产环境的webpack配置
entry: {
app: './src/serve.js'
},
target: 'node',
output: {
libraryTarget: 'commonjs2' // node规范
},
plugins: [
new VueSSRServerPlugin(), // vue-server-renderer/server-plugin
new HtmlWebpackPlugin({
filename: 'index.ssr.html',
template: 'index.ssr.html',
inject: 'true',
files: { js: 'app.js' },
minify: {
// removeComments: true, 需要保留注释
collapseWhitespace: true,
removeAttributeQuotes: true
}
})
}]
- webpack.client.config.js copy生产环境的webpack配置
entry: './src/client.js',
plugins: [
new VueSSRClientPlugin(), // vue-server-renderer/client-plugin
]
- service 运行的node服务器
const app = require('express')()
const { createBundleRenderer } = require('vue-server-renderer')
const serveBuild = require(path.resolve(__dirname,'./dist/vue-ssr-server-bundle.json))
const clientManifest = require(path.resolve(__dirname, './dist/vue-ssr-client-manifest.json'))
const template = fs.readFileSync(path.resolve(__dirname, './dist/index.srr.html'), 'utf-8')
const renderer = createBundleRenderer(serverBuild, {
runInNewContext: false,
template,
clientManifest: clientManifest
})
server.get('*', (req, res) => {
if(req.url != '/favicon.icon') {
const context = { url: req.url }
const ssrStream = renderer.renderToStream(context) // 生成流
let buffers = []
ssrStream.on('error', () => {})
ssrStream.on('data', data => buffers.push(data))
ssrStream.on('end', () => { res.end(
Buffer.concat(buffers)
)})
}
})
server.listen(3000)
ssr框架nuxt/react-next
- pages 页面, 新建文件会自动注册路由
- components 放组件
- assets 放资源文件
- middleware 中间件
- 打包上线: 打包客服端和服务端js -> 开启node服务