搭建自己的SSR

一、渲染一个Vue实例

  • mkdir vue-ssr

  • cd vue-ssr

  • npm init -y

  • npm i vue vue-server-renderder

  • server.js

    const Vue = require('vue')
    const renderer = require('vue-server-renderer').createRenderer()
    const app = new Vue({template: `<div id="app"><h1>{{message}}</h1></div>`,data: {message: '肖战'}
    })renderer.renderToString(app, (err, html) => {if (err) throw errconsole.log(html)
    })
    
  • node server.js,运行结果:

    <div id="app" data-server-rendered="true"><h1>肖战</h1></div>
    

    data-server-rendered="true"这个属性是为了将来客户端渲染激活接管的接口

二、结合到Web服务器中

使用express对所有的get请求都做同样的处理,new一个Vue,使用vue-server-renderer的renderToString的方法传入Vue实例,回调函数中的html就是最终得到的DOM结构

server.js

const Vue = require('vue')
const express = require('express')const renderer = require('vue-server-renderer').createRenderer()const server = express()server.get('/', (req, res) => {const app = new Vue({template: `<div id="app"><h1>{{message}}</h1></div>`,data: {message: '肖战'}})renderer.renderToString(app, (err, html) => {if (err) {return res.status(500).end('Internal Server Error.')}res.setHeader('Content-Type', 'text/html; charset=utf8') // 设置编码,防止乱码res.end(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body>${html}</body></html>`)})
})server.listen(3000, () => {console.log('server running at port 3000...')
})

三、使用HTML模板

1. 创建HTML模板文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><!--vue-ssr-outlet-->
</body>
</html>

<!--vue-ssr-outlet-->是占位符,为了接收将来要渲染的变量,不能写错,不能有多余的空格

2. js代码中的createRenderer方法指定模板文件

server.js

const Vue = require('vue')
const express = require('express')
const fs = require('fs')const renderer = require('vue-server-renderer').createRenderer({// 这里指定模板文件template: fs.readFileSync('./index.template.html', 'utf-8')
})const server = express()server.get('/', (req, res) => {const app = new Vue({template: `<div id="app"><h1>{{message}}</h1></div>`,data: {message: '拉钩教育'}})renderer.renderToString(app, (err, html) => { // 此处的html参数是被模板文件处理过了的,可以直接输出到用户的页面上if (err) {return res.status(500).end('Internal Server Error.')}res.setHeader('Content-Type', 'text/html; charset=utf8') // 设置编码,防止乱码res.end(html)})
})server.listen(3000, () => {console.log('server running at port 3000...')
})

四、在模板中使用外部数据

Index.template.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">{{{ meta }}}<title>{{ title }}</title>
</head>
<body><!--vue-ssr-outlet-->
</body>
</html>

使用两个花括号可以数据外部数据变量,而标签也会进行转义后输出在页面上。此时可以使用三个花括号原样输出数据,不会对标签进行转义处理

在js代码中给renderer.renderToString增加第二个参数为外部数据对象

renderer.renderToString(app, {title: '拉勾教育',meta: `<meta name="description" content="拉勾教育" >`}, (err, html) => {if (err) {return res.status(500).end('Internal Server Error.')}res.setHeader('Content-Type', 'text/html; charset=utf8') // 设置编码,防止乱码res.end(html)})

五、构建配置

1. 基本思路

2. 源码结构

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

App.vue

<template><div id="app"><h1>{{message}}</h1><h2>客户端动态交互</h2><div><input v-model="message"></div><div><button @click="onClick">点击测试</button></div></div>
</template><script>
export default {name: 'App',data: function () {return {message: '拉勾教育'}},methods: {onClick () {console.log('Hello World!')}}
}
</script>

app.js 是我们应用程序的「通用 entry」。在纯客户端应用程序中,我们将在此文件中创建根 Vue 实例,并直接挂载到 DOM。但是,对于服务器端渲染(SSR),责任转移到纯客户端 entry 文件。app.js 简单地使用 export 导出一个 createApp 函数:

import Vue from 'vue'
import App from './App.vue'// 导出一个工厂函数,用于创建新的
// 应用程序、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 导出函数,并在每次渲染中重复调用此函数。此时,除了创建和返回应用程序实例之外,它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配 (server-side route matching) 和数据预取逻辑 (data pre-fetching logic)。

import { createApp } from './app'export default context => {const { app } = createApp()return app
}

3. 安装依赖

(1) 安装生产依赖

npm i vue vue-server-renderer express cross-env
说明
vue Vue.js核心库
vue-server-renderer Vue服务端渲染工具
express 基于Node的webpack服务框架
cross-env 通过npm scripts设置跨平台环境变量

(2) 安装开发依赖

npm i -D webpack webpack-cli webpack-merge webpack-node-externals @babel/core @babel/plugin-transform-runtime @babel/preset-env babel-loader css-loader url-loader file-loader rimraf vue-loader vue-template-compiler friendly-errors-webpack-plugin
说明
webpack webpack核心包
webpack-cli webpack的命令行工具
webpack-merge webpack配置信息合并工具
webpack-node-externals 排除webpack中的Node模块
rimraf 基于Node封装的一个跨平台rm -rf工具
friendly-errors-webpack-plugin 友好的webpack错误提示
@babel/core
@babel/plugin-transform-runtime
@babel/preset-env
babel-loader
Babel相关工具
vue-loader
vue-template-compiler
处理.vue资源
file-loader 处理字体资源
css-loader 处理CSS资源
url-loader 处理图片资源

4. webpack配置文件及打包命令

(1) 初始化webpack打包配置文件

build
|---webpack.base.config.js # 公共配置
|---webpack.client.config.js # 客户端打包配置文件
|---webpack.server.config.js # 服务端打包配置文件

webpack.base.config.js

/*** 公共配置*/
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const path = require('path')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const resolve = file => path.resolve(__dirname, file)const isProd = process.env.NODE_ENV === 'production'module.exports = {mode: isProd ? 'production' : 'development',output: {path: resolve('../dist/'),publicPath: '/dist/',filename: '[name].[chunkhash].js'},resolve: {alias: {// 路径别名,@ 指向 src'@': resolve('../src/')},// 可以省略的扩展名// 当省略扩展名的时候,按照从前往后的顺序依次解析extensions: ['.js', '.vue', '.json']},devtool: isProd ? 'source-map' : 'cheap-module-eval-source-map',module: {rules: [// 处理图片资源{test: /\.(png|jpg|gif)$/i,use: [{loader: 'url-loader',options: {limit: 8192,},},],},// 处理字体资源{test: /\.(woff|woff2|eot|ttf|otf)$/,use: ['file-loader',],},// 处理 .vue 资源{test: /\.vue$/,loader: 'vue-loader'},// 处理 CSS 资源// 它会应用到普通的 `.css` 文件// 以及 `.vue` 文件中的 `<style>` 块{test: /\.css$/,use: ['vue-style-loader','css-loader']},// CSS 预处理器,参考:https://vue-loader.vuejs.org/zh/guide/pre-processors.html// 例如处理 Less 资源// {//   test: /\.less$/,//   use: [//     'vue-style-loader',//     'css-loader',//     'less-loader'//   ]// },]},plugins: [new VueLoaderPlugin(),new FriendlyErrorsWebpackPlugin()]
}

webpack.client.config.js

/*** 客户端打包配置*/
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')module.exports = merge(baseConfig, {entry: {app: './src/entry-client.js'},module: {rules: [// ES6 转 ES5{test: /\.m?js$/,exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env'],cacheDirectory: true,plugins: ['@babel/plugin-transform-runtime']}}},]},// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,// 以便可以在之后正确注入异步 chunk。optimization: {splitChunks: {name: "manifest",minChunks: Infinity}},plugins: [// 此插件在输出目录中生成 `vue-ssr-client-manifest.json`。new VueSSRClientPlugin()]
})

webpack.server.config.js

/*** 服务端打包配置*/
const { merge } = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')module.exports = merge(baseConfig, {// 将 entry 指向应用程序的 server entry 文件entry: './src/entry-server.js',// 这允许 webpack 以 Node 适用方式处理模块加载// 并且还会在编译 Vue 组件时,// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。target: 'node',output: {filename: 'server-bundle.js',// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)libraryTarget: 'commonjs2'},// 不打包 node_modules 第三方包,而是保留 require 方式直接加载externals: [nodeExternals({// 白名单中的资源依然正常打包allowlist: [/\.css$/]})],plugins: [// 这是将服务器的整个输出构建为单个 JSON 文件的插件。// 默认文件名为 `vue-ssr-server-bundle.json`new VueSSRServerPlugin()]
})

5. 配置构建命令

"scripts": {"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js","build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js","build": "rimraf dist && npm run build:client && npm run build:server"}

6. 启动应用

server.js

const Vue = require('vue')
const express = require('express')
const fs = require('fs')const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
const { static } = require('express')
const template = fs.readFileSync('./index.template.html', 'utf-8')
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle, {template,clientManifest
})const server = express()// 请求前缀,使用express中间件的static处理
server.use('/dist', express.static('./dist'))server.get('/', (req, res) => {renderer.renderToString({title: '拉勾教育',meta: `<meta name="description" content="拉勾教育" >`}, (err, html) => {if (err) {return res.status(500).end('Internal Server Error.')}res.setHeader('Content-Type', 'text/html; charset=utf8') // 设置编码,防止乱码res.end(html)})
})server.listen(3001, () => {console.log('server running at port 3001...')
})

7. 解析渲染流程

六、构建配置开发模式

1. 基本思路

生产模式直接渲染,开发模式监视打包构建,重新生成Renderer渲染器

2. 提取处理模块

server.js

const Vue = require('vue')
const express = require('express')
const fs = require('fs')
const createBundleRenderer = require('vue-server-renderer')
const setupDevServer = require('./build/setup-dev-server')const server = express()// 请求前缀,使用express中间件的static处理
server.use('/dist', express.static('./dist'))const isProd = process.env.NODE_ENV === 'production'let renderer
let onReady
if (isProd) {const serverBundle = require('./dist/vue-ssr-server-bundle.json')const clientManifest = require('./dist/vue-ssr-client-manifest.json')const { static } = require('express')const template = fs.readFileSync('./index.template.html', 'utf-8')renderer = createBundleRenderer(serverBundle, {template,clientManifest })
} else {// 开发模式 -> 监视打包构建 -> 重新生成Renderer渲染器onReady = setupDevServer(server, (serverBundle, template, clientManifest) => {renderer = createBundleRenderer(serverBundle, {template,clientManifest })})
}// render 是路由函数
const render = (req, res) => {// renderer是Vue SSR的渲染器renderer.renderToString({title: '拉勾教育',meta: `<meta name="description" content="拉勾教育" >`}, (err, html) => {if (err) {return res.status(500).end('Internal Server Error.')}res.setHeader('Content-Type', 'text/html; charset=utf8') // 设置编码,防止乱码res.end(html)})
}server.get('/', isProd ? render : async (req, res) => {// 等待有了Renderer渲染器以后,调用render进行渲染await onReadyrender()
})server.listen(3001, () => {console.log('server running at port 3001...')
})

build/setup-dev-server.js

module.exports = (server, callback) => {let ready // ready就是promise中的resolveconst onReady = new Promise(r => ready = r)// 监视构建 -> 更新 Rendererlet templatelet serverBundlelet clientManifestreturn onReady}

3. update更新函数

const update = () => {if (template && serverBundle && clientManifest) {ready()callback(serverBundle, template, clientManifest)}
}

4. 处理模板文件

// 监视构建 template -> 调用 update -> 更新 Renderer 渲染器
const templatePath = path.resolve(__dirname, '../index.template.html')
template = fs.readFileSync(templatePath, 'utf-8')
update()
// fs.watch、fs.watchFile
chokidar.watch(templatePath).on('change', () => {template = fs.readFileSync(templatePath, 'utf-8')update()
})

5. 服务端监视打包

// 监视构建 serverBundle -> 调用 update -> 更新 Renderer 渲染器
const serverConfig = require('./webpack.server.config')
// serverCompiler是一个webpack编译器,直接监听资源改变,进行打包构建
const serverCompiler = webpack(serverConfig)
serverCompiler.watch({}, (err, stats) => {if (err) throw errif (stats.hasErrors()) returnserverBundle = JSON.parse(fs.readFileSync(resolve('../dist/vue-ssr-server-bundle.json'), 'utf-8'))console.log(serverBundle)update()
})

6. 把数据写到内存中

// 监视构建 serverBundle -> 调用 update -> 更新 Renderer 渲染器
const serverConfig = require('./webpack.server.config')
const serverCompiler = webpack(serverConfig)
const serverDevMiddleware = devMiddleware(serverCompiler, {logLevel: 'silent' // 关闭日志输出,由 FriendlyErrorsWebpackPlugin 处理
})
serverCompiler.hooks.done.tap('server', () => {serverBundle = JSON.parse(serverDevMiddleware.fileSystem.readFileSync(resolve('../dist/vue-ssr-server-bundle.json'), 'utf-8'))update()
})

7. 客户端构建

// 监视构建 clientManifest -> 调用 update -> 更新 Renderer 渲染器
const clientConfig = require('./webpack.client.config')
clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin())
clientConfig.entry.app = ['webpack-hot-middleware/client?quiet=true&reload=true', // 和服务端交互处理热更新一个客户端脚本clientConfig.entry.app
]
clientConfig.output.filename = '[name].js' // 热更新模式下确保一致的 hash
const clientCompiler = webpack(clientConfig)
const clientDevMiddleware = devMiddleware(clientCompiler, {publicPath: clientConfig.output.publicPath,logLevel: 'silent' // 关闭日志输出,由 FriendlyErrorsWebpackPlugin 处理
})
clientCompiler.hooks.done.tap('client', () => {clientManifest = JSON.parse(clientDevMiddleware.fileSystem.readFileSync(resolve('../dist/vue-ssr-client-manifest.json'), 'utf-8'))update()
})

8. 热更新

clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin())clientConfig.entry.app = ['webpack-hot-middleware/client?quiet=true&reload=true', // 和服务端交互处理热更新一个客户端脚本clientConfig.entry.app
]
clientConfig.output.filename = '[name].js' // 热更新模式下确保一致的 hashconst hotMiddleware = require('webpack-hot-middleware')server.use(hotMiddleware(clientCompiler, {log: false // 关闭它本身的日志输出
}))

七、编写通用应用注意事项

八、路由处理

1. 配置Vue-Router

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/src/pages/Home'Vue.use(VueRouter)export const createRouter = () => {const router = new VueRouter({mode: 'history', // 兼容前后端,routes: [{path: '/',name: 'home',component: Home},{path: '/about',name: 'about',component: () => import('@/src/pages/About')},{path: '*',name: 'error404',component: () => import('@/src/pages/404')}]})return router // 千万别忘了返回router
}

2. 将路由注册到根实例

app.js

/*** 同构应用通用启动入口*/
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router/'// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {const router = createRouter()const app = new Vue({router, // 把路由挂载到Vue根实例当中// 根实例简单的渲染应用程序组件。render: h => h(App)})return { app, router }
}

3. 适配服务端入口

拷贝官网上提供的entry-server.js

// entry-server.js
import { createApp } from './app'export default context => {// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,// 以便服务器能够等待所有的内容在渲染前,// 就已经准备就绪。return new Promise((resolve, reject) => {const { app, router } = createApp()// 设置服务器端 router 的位置router.push(context.url)// 等到 router 将可能的异步组件和钩子函数解析完router.onReady(() => {const matchedComponents = router.getMatchedComponents()// 匹配不到的路由,执行 reject 函数,并返回 404if (!matchedComponents.length) {return reject({ code: 404 })}// Promise 应该 resolve 应用程序实例,以便它可以渲染resolve(app)}, reject)})
}

路由表里已经配置过404页面了,所以不用额外判断404,然后将Promise改成async/await的形式,最终如下:

// entry-server.js
import { createApp } from './app'export default async context => {// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,// 以便服务器能够等待所有的内容在渲染前,// 就已经准备就绪。const { app, router } = createApp()// 设置服务器端 router 的位置router.push(context.url)// 等到 router 将可能的异步组件和钩子函数解析完await new Promise(router.onReady.bind(router))return app
}

4. 服务端server适配

我们的服务器代码使用了一个 * 处理程序,它接受任意 URL。这允许我们将访问的 URL 传递到我们的 Vue 应用程序中,然后对客户端和服务器复用相同的路由配置!

server.js处理

// ...// render 是路由函数
const render =async (req, res) => {// renderer是Vue SSR的渲染器try {const html = await renderer.renderToString({title: '拉勾教育',meta: `<meta name="description" content="拉勾教育" >`,url: req.url})res.setHeader('Content-Type', 'text/html; charset=utf8') // 设置编码,防止乱码res.end(html)}catch(err) {res.status(500).end('Internal Server Error.')}
}// 服务端路由匹配为*,意味着所有的路由都会进入这里
server.get('*', isProd ? render : async (req, res) => {// 等待有了Renderer渲染器以后,调用render进行渲染await onReadyrender(req, res)
})// ...

5. 适配客户端入口

需要注意的是,你仍然需要在挂载 app 之前调用 router.onReady,因为路由器必须要提前解析路由配置中的异步组件,才能正确地调用组件中可能存在的路由钩子。这一步我们已经在我们的服务器入口 (server entry) 中实现过了,现在我们只需要更新客户端入口 (client entry):

// entry-client.jsimport { createApp } from './app'const { app, router } = createApp()router.onReady(() => {app.$mount('#app')
})

6. 处理完成

路由出口:

App.vue

<div id="app"><ul><li><router-link to="/">Home</router-link></li><li><router-link to="/about">About</router-link></li></ul><!-- 路由出口 --><router-view/>
</div>

八、管理页面

1. Head 内容

npm install vue-meta

在src/app.js里面,增加代码

import VueMeta from 'vue-meta'Vue.use(VueMeta)Vue.mixin({metaInfo: {titleTemplate: '%s - 拉勾教育'}
})

在entry-server.js的导出函数里,增加代码:

const meta = app.$meta()
context.meta = meta

将meta数据注入到模板页面index.template.html中:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">{{{ meta.inject().title.text() }}}{{{ meta.inject().meta.text() }}}
</head>
<body><!--vue-ssr-outlet-->
</body>
</html>

在vue页面中的应用:

export default {name: 'Home',metaInfo: {title: '首页'}
}
export default {name: 'About',metaInfo: {title: '关于'}
}

九、数据预取和状态管理

1. 思路分析

在服务器端渲染(SSR)期间,我们本质上是在渲染我们应用程序的"快照",所以如果应用程序依赖于一些异步数据,那么在开始渲染过程之前,需要先预取和解析好这些数据

另一个需要关注的问题是在客户端,在挂载 (mount) 到客户端应用程序之前,需要获取到与服务器端应用程序完全相同的数据 - 否则,客户端应用程序会因为使用与服务器端应用程序不同的状态,然后导致混合失败。

为了解决这个问题,获取的数据需要位于视图组件之外,即放置在专门的数据预取存储容器(data store)或"状态容器(state container))"中。首先,在服务器端,我们可以在渲染之前预取数据,并将数据填充到 store 中。此外,我们将在 HTML 中序列化(serialize)和内联预置(inline)状态。这样,在挂载(mount)到客户端应用程序之前,可以直接从 store 获取到内联预置(inline)状态。

2. 数据预取

npm install vuex

创建src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'Vue.use(Vuex)export const createStore = () => {return new Vuex.Store({state: () => ({posts: []}),mutations: {setPosts (state, data) {state.posts = data}},actions: {// 在服务端渲染期间,务必让action返回一个promise async getPosts ({commit}) { // async默认返回Promise// return new Promise()const { data } = await axios.get('https://cnodejs.org/api/v1/topics')commit('setPosts', data.data)}}})
}

将容器注入到入口文件src/app.js

/*** 同构应用通用启动入口*/
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router/'
import VueMeat from 'vue-meta'
import { createStore } from './store'Vue.use(VueMeta)Vue.mixin({metaInfo: {titleTemplate: '%s - 拉勾教育'}
})// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {const router = createRouter()const store = createStore()const app = new Vue({router, // 把路由挂载到Vue根实例当中store, // 把容器挂载到Vue根实例中// 根实例简单的渲染应用程序组件。render: h => h(App)})return { app, router, store }
}

页面pages/Posts.vue,使用serverPrefetch方法在服务端发起异步请求。

<template><div><h1>Post List</h1><ul><li v-for="post in posts" :key="post.id">{{ post.title }}</li></ul></div>
</template><script>
// import axios from 'axios'
import { mapState, mapActions } from 'vuex'export default {name: 'PostList',metaInfo: {title: 'Posts'},data () {return {// posts: []}},computed: {...mapState(['posts'])},// Vue SSR 特殊为服务端渲染提供的一个生命周期钩子函数serverPrefetch () {// 发起 action,返回 Promise// this.$store.dispatch('getPosts')return this.getPosts()},methods: {...mapActions(['getPosts'])}// 服务端渲染//     只支持 beforeCreate 和 created//     不会等待 beforeCreate 和 created 中的异步操作//     不支持响应式数据// 所有这种做法在服务端渲染中是不会工作的!!!// async created () {//   console.log('Posts Created Start')//   const { data } = await axios({//     method: 'GET',//     url: 'https://cnodejs.org/api/v1/topics'//   })//   this.posts = data.data//   console.log('Posts Created End')// }
}
</script><style></style>

3. 将数据预取同步到客户端

entry-server.js

// entry-server.js
import { createApp } from './app'export default async context => {// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,// 以便服务器能够等待所有的内容在渲染前,// 就已经准备就绪。const { app, router, store } = createApp()const meta = app.$meta()// 设置服务器端 router 的位置router.push(context.url)context.meta = meta// 等到 router 将可能的异步组件和钩子函数解析完await new Promise(router.onReady.bind(router))// 这个rendered函数会在服务端渲染完毕之后被调用context.rendered = () => {// Renderer会把 context.state 数据对象内联到页面模板中// 最终发送到客户端的页面中会包含一段脚本:window.__INITIAL_STATE__ = context.state// 客户端就要把页面中的 window.__INITIAL_STATE__ 拿出来填充到客户端 store 容器中 context.state = store.state}return app
}

entry-client.js

// entry-client.jsimport { createApp } from './app'const { app, router, store } = createApp()if (window.__INITIAL_STATE__) {store.replaceState(window.__INITIAL_STATE__)
}router.onReady(() => {app.$mount('#app')
})

3-4-搭建自己的vue-ssr相关推荐

  1. 服务器端渲染-Vue SSR搭建

    阅读建议:建议通过左侧导航栏进行阅读 文章简介:本文是Vue.js服务器端渲染的另一种解决方案-SSR(Server-Side Rendering)学习笔记 Vue SSR是什么 官方文档解释:Vue ...

  2. 理解vue ssr原理,自己搭建简单的ssr框架

    理解vue ssr原理,自己搭建简单的ssr框架 前言 大多数Vue项目要支持SSR应该是为了SEO考虑,毕竟对于WEB应用来说,搜索引擎是一个很大的流量入口.Vue SSR现在已经比较成熟了,但是如 ...

  3. 了解 Vue SSR 这一篇足以

    文章目录 1 - 什么是服务器端渲染? 1.1 新建server文件夹 1.2 生成一个node项目 1.3 安装express 1.4 服务端渲染小案例 1.5 运行查看效果 1.6 打开浏览器 1 ...

  4. 【服务端渲染】之 Vue SSR

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:内容较多,建议通过左侧导航栏进行阅读 Vue SSR 基本介绍 Vue SSR 是什么 官方文档:https://ssr.vuejs.org/ V ...

  5. Vue SSR(Vue2 + Koa2 + Webpack4)配置指南

    正如Vue官方所说,SSR配置适合已经熟悉 Vue, webpack 和 Node.js 开发的开发者阅读.请先移步 ssr.vuejs.org 了解手工进行SSR配置的基本内容. 从头搭建一个服务端 ...

  6. Vue SSR 性能优化实践

    齐云雷,微医云服务团队前端工程师,本文是作者在<第二届缤纷前端技术沙龙>分享主题的文字版. 估计大部分读者对标题中的性能优化更感兴趣,可惜我分享的重点其实更多在于实践.实践有深有浅,下面介 ...

  7. Vue SSR 渲染 Nuxt3 入门学习

    Vue SSR 渲染 Nuxt3 入门学习 SPA应用:也就是单页应用,这些多是在客户端的应用,不利于进行SEO优化(搜索引擎优化). SSR应用:在服务端进行渲染,渲染完成后返回给客户端,每个页面有 ...

  8. 自定义vue SSR

    项目源码地址 : https://github.com/qifutian/learngit/tree/main/vue-ssr 搭建自己的SSR mkdir vue-ssr cd vue-ssr np ...

  9. 关于Vue ssr的一点探讨

    这很难,里面只是我以比较明显的一个问题,引发对整个ssr的研究.但是我又复习了下Vuex,发现了异步问题.过两天把router也复习了.那异步问题应该就解决了,到时候再出篇稿子.这篇,你可能看不懂,因 ...

  10. Vue SSR 服务端渲染原理(简易版本)

    前言 在了解Vue SSR之前,我们要搞明白两个东西先:SSR 和 浏览器的渲染, 涉及到的技术: Vue vue-server-renderer Nodejs Express 1. 什么是SSR S ...

最新文章

  1. C++ SSE运算例子
  2. AM-GM均值不等式的一种简证
  3. 【struts2+hibernate+spring项目实战】分页功能的完整的实现(通用分页、基类实现)
  4. OV7725学习之SCCB协议(一)
  5. 第八十四节,css布局小技巧及font-awesome图标使用
  6. javaone_JavaOne 2012:在JVM上诊断应用程序
  7. 【clickhouse】clickhouse Exception: Table is in readonly mode
  8. Select显示多级分类列表
  9. 老项目Xcode5.1编译器错误
  10. java对数据库的基础知识
  11. C# Access 读写数据库
  12. 【技术综述】一文道尽传统图像降噪方法
  13. HTML小游戏2—— 2048网页版(附完整源码)
  14. 逆向分析中加解密算法常用工具
  15. ctc系统通信前置服务器,CTC系统包括哪些接口服务器?
  16. android自动亮度流程,Android 亮度自动调节是如何实现的?
  17. 万字拆解飞鹤奶粉:4年增收150亿背后的增长策略是什么?
  18. 实现公网访问树莓派4B(花生壳内网穿透)
  19. Excel技巧 - 换行符用法
  20. 流量为王:ABTest流量分层分桶机制

热门文章

  1. Java Web概述-练习题
  2. MS-DOS系统的操作命令
  3. iPhone支持杜比的机型
  4. 【机器学习】阿里云天池竞赛——工业蒸汽量预测(6)
  5. 卖服务器销售打广告语句子,卖灯销售广告语
  6. canvas星空连线背景
  7. 微信公众平台开发1-开发配置
  8. 线性代数1:向量、线性组合、张成的空间和基
  9. 企业微信如何设置通讯录权限?
  10. 在线预览 Word、Excel、PowerPoint 文档——Office Online插件使用