Vue SSR(服务端渲染)

Vue 2021-03-13 3783

什么是SSR?

你可能会想到某游戏或者梯子,再或者某牛奶,没错我也是这么想的。简单理解就是原来由客户端生成的html,现在由服务端渲染后,在交给浏览器,这样可以理解为啥ssr的首屏渲染快了,

再就是ssr的SEO能力好,国内的搜索引擎好像都不太支持js动态网站,当然首页会给你收录,这样如果你的网站是动态的,seo相比会差很多。

与ssr相对的,还有预渲染(Prerendering, 预渲染直接吐真正的页面给浏览器。),其实我不太懂,我猜想可能是部分页面直接使用html页面

SSR优势?

https://ssr.vuejs.org/zh/#为什么使用服务器端渲染-ssr-?

不要为了使用SSR而使用SSR,SSR相比传统模板或者SPA开发来说,技术难度和运维成本都要高

Vue-SSR

vue的ssr主要借助vue-server-renderer,它能将Vue实例渲染为html,而核心就是将原来浏览器挂载Vue,交给服务端了。

安装:

cnpm install vue vue-server-renderer --save

PS: 

  • vue-server-renderer 和 vue 必须匹配版本。
  • vue-server-renderer 依赖一些 Node.js 原生模块,因此只能在 Node.js 中使用。

基础的SSR项目参考:https://ssr.vuejs.org/zh/guide/#完整实例代码

基本的项目构建,参考下图:

 基本的项目结构参考:

src
├── components
│   ├── Index.vue
│   ├── Detail.vue
├── App.vue
├── app.js # 通用 entry(universal entry)
├── entry-client.js # 仅运行于浏览器
└── entry-server.js # 仅运行于服务器

app.js

导出一个工厂函数,用于创建新的,应用程序、router 和 store 实例

// 服务端入口
// 创建vue实例

import Vue from 'vue'
// 注意一定要使用App.vue而非App,不然会与app.js冲突
import App from './App.vue'
import createRouter from './router/index2'
import { createStore } from './store/store'
import { sync } from 'vuex-router-sync'

export default function createApp () {
  const router = createRouter()
  const store = createStore()

  // 同步路由状态(route state)到 store
  sync(store, router)

  const app = new Vue({
    router,
    store,
    render: h => h(App)
  })
  return { app, router, store }
}

entry-client.js

挂载应用程序,Vue 在浏览器端接管由服务端发送的静态 HTML,使其变为由 Vue 管理的动态 DOM 的过程。

import Vue from 'vue'
// 挂载、激活app
import createApp from './app'

const { app, router, store } = createApp()

// 客户端预取数据
Vue.mixin({
  beforeMount () {
    const { asyncData } = this.$options
    if (asyncData) {
      asyncData({
        store: this.$store,
        route: this.$route
      })
    }
  }
})

// 当使用 template 时,context.state 将作为 window.__INITIAL_STATE__ 状态,
// 自动嵌入到最终的 HTML 中
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}
router.onReady(() => {
  app.$mount('#app')
})

entry-server.js

服务器 entry 使用 default export 导出函数,并在每次渲染中重复调用此函数。此文件还可以执行服务器端路由匹配和数据预取逻辑

// 渲染首屏
import createApp from '../src/app'

// 5
export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    // 进入首屏
    router.push(context.url)
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        // eslint-disable-next-line prefer-promise-reject-errors
        return reject({ code: 404 })
      }
      // 对所有匹配的路由组件调用 `asyncData()`
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute
          })
        }
      })).then(() => {
        // 在所有预取钩子(preFetch hook) resolve 后,
        // 我们的 store 现在已经填充入渲染应用程序所需的状态。
        // 当我们将状态附加到上下文,
        // 并且 `template` 选项用于 renderer 时,
        // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
        context.state = store.state

        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

与传统SPA应用相比,它是直接使用服务端启动,因此你需要一个服务端脚本:

// eslint-disable-next-line no-unused-vars
const Vue = require('vue')
const express = require('express')
const app = express()
const fs = require('fs')
const path = require('path')

// 创建渲染器
const { createBundleRenderer } = require('vue-server-renderer')
const serverBundle = require('../dist/server/vue-ssr-server-bundle.json')

const clientManifest = require('../dist/client/vue-ssr-client-manifest.json')
const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false,
  template: fs.readFileSync('../public/index.temp.html', 'utf-8'), // 宿主模板文件
  clientManifest
})

// 中间件处理静态文件请求
app.use(express.static(path.resolve(__dirname, '../dist/client'), { index: false }))

// 路由处理交给vue
app.get('*', async (req, res) => {
  try {
    const context = {
      url: req.url,
      title: 'ssr test'
    }
    // 将vue实例转为html
    const html = await renderer.renderToString(context)
    // console.log(html)
    res.send(html)
  } catch (e) {
    res.send('服务端异常')
  }
})

app.listen(8000, () => {
  console.log('listen 8000')
})

这里省去了webpack配置。

效果:

完整项目地址:点击访问

 

标签:Vue

文章评论

评论列表

已有2条评论