Vue SSR服务端渲染 VueX预取数据 实现SEO优化

Vue SSR服务端渲染

  1. 服务端渲染获取页面静态数据
  2. VueX 预取ajax(后台)数据

/src下创建entry-client.js、entry-server.js文件

在这里插入图片描述
entry-client.js

import Vue from 'vue'
import { createApp } from './main.js'
const { app, router, store } = createApp()if (window.__INITIAL_STATE__) {store.replaceState(window.__INITIAL_STATE__)
}router.onReady(() => {router.beforeResolve((to, from, next) => {// 添加路由钩子函数,用于处理 asyncData.// 在初始路由 resolve 后执行,// 以便我们不会二次预取(double-fetch)已有的数据。// 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。const matched = router.getMatchedComponents(to)const prevMatched = router.getMatchedComponents(from)// 我们只关心非预渲染的组件// 所以我们对比它们,找出两个匹配列表的差异组件let diffed = falseconst activated = matched.filter((c, i) => {return diffed || (diffed = (prevMatched[i] !== c))})if (!activated.length) {return next()}// 这里如果有加载指示器 (loading indicator),就触发Promise.all(activated.map(c => {if (c.asyncData) {return c.asyncData({ store, route: to })}})).then(() => {// 停止加载指示器(loading indicator)next()}).catch(next)})app.$mount('#app')
})Vue.mixin({beforeRouteUpdate(to, from, next) {const { asyncData } = this.$optionsif (asyncData) {asyncData({store: this.store,route: to}).then(next).catch(next)} else {next()}}
})

entry-server.js

import { createApp } from './main.js'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) {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.stateresolve(app)}).catch(reject)}, reject)})
}

现在为预取数据打下基础,当然无论你是否需要预取数据都可以使用这一段

/src/store 下新建api.js

import {url
} from '../util/https.js'//这是为了区分生产环境和开发环境的一个变量而已
//可以将url改成你们的ip地址
//上线时改成服务器地址import axios from "axios"
export function fetchItem(item) {return new Promise(function(resolve, reject) {axios({url: url + item.url,method: item.type,params: item.params}).then(function(res) {resolve(JSON.stringify(res.data));})})
}

/src/store/index.js VueX改为函数导出

import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)import {fetchItem
} from './api'export function createStore() {return new Vuex.Store({state: {},mutations: {setItem(state, {id,item}) {Vue.set(state.items, id, JSON.parse(item))}},actions: {// `store.dispatch()` 会返回 Promise,// 以便我们能够知道数据在何时更新fetchItem({commit}, item) {return fetchItem(item).then(rel => {commit('setItem', {id: item.id,item: rel})})}}});
}

/router/index.js 将路由改为函数导出


export function createRouter() {return new VueRouter({mode: 'history', //一定要是history模式base: process.env.BASE_URL,routes: []})
}

/main.js 中间省略了无关内容

1.中导入router和vuex 安装vuex-router-sync并导入
import { createRouter } from '@/router'
import { createStore } from "@/store";
import { sync } from 'vuex-router-sync'2.new Vue 这一块 改成函数 导出app、router、store 
export function createApp() {const router = createRouter();const store = createStore()  // +sync(store, router)const app = new Vue({router,store,      // +render: h => h(App)});return { app, router,store };
}

public文件夹下增加index.template.html index.html我删掉了,因为没用
在这里插入图片描述
这个html文件内需要有这句注释并放置在body内,因为他会将vue里面的内容渲染后与这句注释替换(大致是这么个意思)
在这里插入图片描述
package.json 里面的scripts对象中增加

 "build:client": "vue-cli-service build","build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server","build:win": "npm run build:server && move dist\\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\\vue-ssr-server-bundle.json"

vue.config.js

// vue.config.jsconst VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";
// 启用css提取
//开启gzip
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
const CompressionWebpackPlugin = require('compression-webpack-plugin');module.exports = {productionSourceMap: false,publicPath: '/',outputDir: 'dist', //打包时生成的生产环境构建文件的目录assetsDir: 'staticNte', // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录lintOnSave: false,css: {extract: false, // 是否使用css分离插件 ExtractTextPlugin// sourceMap: false, // 开启 CSS source maps// loaderOptions: {}, // css预设器配置项// modules: false // 启用 CSS modules for all css / pre-processor files.},devServer: {open: true, //是否自动弹出浏览器页面 host: '0.0.0.0',disableHostCheck: true, //解决域名访问本地运行地址时出现Invalid Host header的问题 port: 8054, //端口proxy: {'/api': {ws: true,changeOrigin: true, // 表示是否跨域target: '需要跨域的地址', //pathRewrite: {'^/api': '' //重写接口}}}},configureWebpack: () => ({// 将 entry 指向应用程序的 server / client 文件entry: `./src/entry-${target}.js`,// 对 bundle renderer 提供 source map 支持devtool: 'source-map',target: TARGET_NODE ? "node" : "web",node: TARGET_NODE ? undefined : false,output: {libraryTarget: TARGET_NODE ? "commonjs2" : undefined},// https://webpack.js.org/configuration/externals/#function// https://github.com/liady/webpack-node-externals// 外置化应用程序依赖模块。可以使服务器构建速度更快,// 并生成较小的 bundle 文件。externals: TARGET_NODE ?nodeExternals({// 不要外置化 webpack 需要处理的依赖模块。// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单whitelist: [/\.css$/, /demo-lib/],allowlist: /\.css$/}) : process.env.NODE_ENV == 'development' ?  undefined : {'vue': 'Vue','vue-router': 'VueRouter','vuex': 'Vuex','axios': 'axios',// 'element-ui': 'ELEMENT'},optimization: {splitChunks: TARGET_NODE ? false : undefined},plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin(), new CompressionWebpackPlugin({filename: '[path].gz[query]',algorithm: 'gzip',test: productionGzipExtensions,threshold: 1024,minRatio: 0.8})]}),chainWebpack: config => {config.module.rule("vue").use("vue-loader").tap(options => {merge(options, {optimizeSSR: false});});}
};

在这里插入图片描述
根目录下执行npm run build:win (打包命令)
也就是上一步增加的命令
在这里插入图片描述
打包后的dist文件会多出这两个json文件 .gz 文件是开启了gzip
在这里插入图片描述
根目录下创建service文件 并在里面创建index.js
这个是为了启动node服务,将编译好的Vue放置在index.template.html中,如果会的话请忽略
在这里插入图片描述
service/index.js
我个人习惯用espress 你也可以换其他中间件
只是为了启动服务

const express = require("express");
const Vue = require("vue");
const fs = require("fs");
const axios = require('axios')
const qs = require('qs')
const app = express();// 创建渲染器
const {createBundleRenderer
} = require("vue-server-renderer");
const serverBundle = require("../dist/vue-ssr-server-bundle.json");
const clientManifest = require("../dist/vue-ssr-client-manifest.json");
const renderer = createBundleRenderer(serverBundle, {runInNewContext: false,template: fs.readFileSync("../dist/index.template.html", "utf-8"), // 宿主模板文件clientManifest
});// 中间件处理静态文件请求
app.use(express.static("../dist", {index: false
}));app.get("*", async (req, res) => {try{const context = {url: req.url};//获取编译后的完成htmlhtml = await renderer.renderToString(context);res.send(htmlZip);}catch (error) {res.status(500).send(html);}
})

到此就基本结束了前端的活

部署
服务器安装node环境
安装pm2
通过pm2 start index.js -i max 这一句是为了开启负载均衡 max可以为数字,max是根据服务器cpu核数自动开,是几核就开几核,数字是你想开几核就开几核
开启的端口是3001
用的服务器的阿里云 安全组开放 3001的端口
nginx配置代理

注意事项
node开启的中间件处理静态文件请求可以关闭通过nginx开启
可以通过node处理跨域问题,页面请求node,node请求服务器。达到转发
加载慢可以使用外部的cdn
vue开启gzip,nginx同时配置gzip
页面级别缓存 这个网上有可以找一找,出现乱码查看index.template.html里面的meta中是否有utf-8

到此结束

第一次写这种东西,不知道有没有遗漏
http://www.sensorservice.com
实际的项目
产品详情和文章详情使用了预取数据
github
只实现了服务端渲染 vuex预取数据没有加 ,可以看上面加
有时间的话再加vuex预取数据吧


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部