Vue-SSR

1,045 阅读3分钟

服务端渲染(SSR)是将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

和纯客户端的单页面应用不同(SPA),服务端渲染可以解决 首屏渲染慢,SEO搜索的问题。

Vue的SSR可以用Nuxt.js实现,以及使用Vue-SSR实现。

Vue-SSR实现原理

实现原理:Source部分就是我们所编写的源代码,所有代码有一个公共入口,就是app.js,紧接着就是服务端的入口

(entry-server.js)和客户端的入口(entry-client.js)。当完成所有源代码的编写之后,我们通过webpack的构建,打包出两个bundle,分别是server bundle和client bundle;当用户进行页面访问的时候,先是经过服务端的入口,将vue组建组装为html字符串,并混入客户端所访问的html模板中,最终就完成了整个ssr渲染的过程。

简单实现

安装

npm install vue vue-server-renderer --save

目录结构

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

app.js是应用程序的通用入口,入口应该为每个请求创建一个新的跟Vue实例。因为Node.js是长期运行的进程,如果我们在多个请求之间使用一个共享的实例,很容易导致交叉请求状态污染 (cross-request state pollution)。

因此,我们不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例,这与每个用户在自己的浏览器中使用新应用程序的实例类似。

// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
  const app = new Vue({
    // 根实例简单的渲染应用程序组件。
    render: h => h(App)
  })
  return { app }
}

entry-client.js客户端 entry 只需创建应用程序,并且将其挂载到 DOM 中

import { createApp } from './app'

const { app } = createApp()

// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')

entry-server.js服务器 entry 使用 default export 导出函数,并在每次渲染中重复调用此函数

import { createApp } from './app'

export default context => {
  const { app } = createApp()
  return app
}

用Vue-Router实现路由

和创建Vue实例相似,返回一个函数

// router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export function createRouter () {
  return new Router({
    mode: 'history', // 兼容服务器端
    routes: [
      // ...
    ]
  })
}

在app.js中加入Vue-Router

export function createApp () {
  // 创建 router 实例
  const router = createRouter()

  const app = new Vue({
    // 注入 router 到根 Vue 实例
    router,
    render: h => h(App)
  })

  // 返回 app 和 router
  return { app, router }
}

在entry-server.js中实现服务端路由逻辑

export default async context => {
  // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
    // 以便服务器能够等待所有的内容在渲染前,
    // 就已经准备就绪。
    const { app, router } = createApp()

    // 设置服务器端 router 的位置
    router.push(context.url)

    // 等到 router 将可能的异步组件和钩子函数解析完
    await new Promise(router.onReady.bind(router))
    return app
}