我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Vue 服务端渲染SSR示例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Vue 服务端渲染SSR示例详解

手写Vue服务端渲染

概念:放在浏览器进行就是浏览器渲染,放在服务器进行就是服务器渲染。

  • 客户端渲染不利于 SEO 搜索引擎优化
  • 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
  • SSR直接将HTML字符串传递给浏览器。大大加快了首屏加载时间。
  • SSR占用更多的CPU和内存资源
  • 一些常用的浏览器API可能无法正常使用
  • 在vue中只支持beforeCreate和created两个生命周期

一.开始vue-ssr之旅

yarn add vue-server-renderer vue
yarn add koa koa-router

createRenderer,创建一个渲染函数 renderToString, 渲染出一个字符串

const Vue = require('vue');
const render = require('vue-server-renderer');
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
const vm = new Vue({
    data(){
        return {msg:"hello world"}
    },
    template:`<div>{{msg}}</div>`
});
router.get('/',async (ctx)=>{
    let r = await render.createRenderer().renderToString(vm);
    ctx.body = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        ${r}
    </body>
    </html>
    `
});
app.use(router.routes());
app.listen(4000);

二.采用模板渲染

<!DOCTYPE html>
<html lang="en">
  <head><title>Hello</title></head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>

传入template 替换掉注释标签

const Vue = require('vue');
const render = require('vue-server-renderer');
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
const vm = new Vue({
    data(){
        return {msg:"hello world"}
    },
    template:`<div>{{msg}}</div>`
});
const template = require('fs').readFileSync('./index.html','utf8');
router.get('/',async (ctx)=>{
    let r = await render.createRenderer({
        template
    }).renderToString(vm);
    ctx.body = r;
});
app.use(router.routes());
app.listen(4000);

三.ssr目录创建

├── config
│   ├── webpack.base.js
│   ├── webpack.client.js
│   └── webpack.server.js
├── dist
│   ├── client.bundle.js
│   ├── index.html
│   ├── index.ssr.html
│   ├── server.bundle.js
│   ├── vue-ssr-client-manifest.json
│   └── vue-ssr-server-bundle.json
├── package.json
├── public
│   ├── index.html
│   └── index.ssr.html
├── server.js
├── class="lazy" data-src
│   ├── App.vue
│   ├── components
│   │   ├── Bar.vue
│   │   └── Foo.vue
│   ├── entry-client.js
│   ├── entry-server.js
│   ├── app.js
│   ├── router.js
│   └── store.js
├── webpack.config.js

四.通过webpack实现编译vue项目

安装插件

yarn add webpack webpack-cli webpack-dev-server vue-loader vue-style-loader css-loader html-webpack-plugin @babel/core @babel/preset-env babel-loader vue-template-compiler webpack-merge
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const resolve = (dir)=>{
    return path.resolve(__dirname,dir)
}
module.exports = {
    entry: resolve('./class="lazy" data-src/client-entry.js'),
    output:{
        filename:'[name].bundle.js',
        path:resolve('dist')
    },
    module:{
        rules:[
            {
                test:/.css$/,
                use:['vue-style-loader','css-loader']
            },
            {
                test:/.js$/,
                use:{
                    loader:'babel-loader',
                    options:{
                        presets:['@babel/preset-env']
                    }
                },
                exclude:/node_modules/
            },
            {
                test:/.vue$/,
                use:'vue-loader'
            }
        ]
    },
    plugins:[
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin({
            template:'./index.html'
        })
    ]
}

app.js

import Vue from "vue";
import App from "./App.vue";
export default () => { // 为了保证实例的唯一性所以导出一个创建实例的函数
  const app = new Vue({
    render: h => h(App)
  });
  return { app };
};

client-entry.js

import createApp from "./app";
const { app } = createApp();
app.$mount("#app"); // 客户端渲染手动挂载到dom元素上

server-entry.js

import createApp from "./app";
export default () => {
  const { app } = createApp();
  return app; // 服务端渲染只需将渲染的实例导出即可
};

五.配置客户端打包和服务端打包

  • webpack.base.js
let path = require('path');
let VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
    output:{
        filename:'[name].bundle.js',
        path:path.resolve(__dirname,'../dist')
    },
    module:{
        rules:[
            {test:/.css/,use:['vue-style-loader','css-loader']},
            {
                test:/.js/,
                use:{
                    loader:'babel-loader',
                    options:{
                        presets:['@babel/preset-env']
                     },
                },
                exclude:/node_modules/,
            },
            {test:/.vue/,use:'vue-loader'}
        ]
    },
    plugins:[
        new VueLoaderPlugin()
    ]
}
  • webpack.client.js
const merge = require("webpack-merge");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const base = require("./webpack.base");
const resolve = filepath => {
  return path.resolve(__dirname, filepath);
};
module.exports = merge(base, {
  entry: {
    client: resolve("../class="lazy" data-src/client-entry.js")
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: resolve("../template/index.client.html")
    })
  ]
});
  • webpack.server.js
const merge = require("webpack-merge");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const base = require("./webpack.base");
const resolve = filepath => {
  return path.resolve(__dirname, filepath);
};
module.exports = merge(base, {
  entry: {
    server: resolve("../class="lazy" data-src/server-entry.js")
  },
  target: "node",
  output: {
    libraryTarget: "commonjs2" // 导出供服务端渲染来使用
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.ssr.html",
      template: resolve("../template/index.ssr.html"),
      excludeChunks: ["server"]
    })
  ]
});

六.配置运行脚本

"scripts": {
    "client:dev": "webpack-dev-server --config ./build/webpack.client.js", // 客户端开发环境
    "client:build": "webpack --config ./build/webpack.client.js", // 客户端打包环境
    "server:build": "webpack --config ./build/webpack.server.js" // 服务端打包环境
 },

七.服务端配置

在App.vue上增加id="app"可以保证元素被正常激活

const Koa = require("koa");
const Router = require("koa-router");
const static = require("koa-static");
const path = require("path");
const app = new Koa();
const router = new Router();
const VueServerRenderer = require("vue-server-renderer");
const fs = require("fs");
// 服务端打包的结果
const serverBundle = fs.readFileSync("./dist/server.bundle.js", "utf8");
const template = fs.readFileSync("./dist/index.ssr.html", "utf8");
const render = VueServerRenderer.createBundleRenderer(serverBundle, {
  template
});
router.get("/", async ctx => {
  ctx.body = await new Promise((resolve, reject) => {
    render.renderToString((err, html) => {
      // 必须写成回调函数的方式否则样式不生效
      resolve(html);
    });
  });
});
app.use(router.routes());
app.use(static(path.resolve(__dirname, "dist")));
app.listen(3000);

在index.ssr.html中需要手动引入客户端打包后的结果

七.通过json配置createBundleRenderer方法

实现热更新,自动增加preload和prefetch,以及可以使用sourceMap

const VueSclass="lazy" data-srclientPlugin = require('vue-server-renderer/client-plugin'); // 在客户端打包时增加插件
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin'); // 在服务端打包时增加插件


const Koa = require("koa");
const Router = require("koa-router");
const static = require("koa-static");
const path = require("path");
const app = new Koa();
const router = new Router();
const VueServerRenderer = require("vue-server-renderer");
const fs = require("fs");
// 服务端打包的结果
// const serverBundle = fs.readFileSync("./dist/server.bundle.js", "utf8");
const template = fs.readFileSync("./dist/index.ssr.html", "utf8");
const serverBundle = require("./dist/vue-ssr-server-bundle.json");
const clientManifest = require("./dist/vue-ssr-client-manifest.json");
const render = VueServerRenderer.createBundleRenderer(serverBundle, {
  template,
  clientManifest // 自动注入客户端打包后的文件
});

router.get("/", async ctx => {
  ctx.body = await new Promise((resolve, reject) => {
    render.renderToString((err, html) => {
      // 必须写成回调函数的方式否则样式不生效
      resolve(html);
    });
  });
});
app.use(router.routes());
app.use(static(path.resolve(__dirname, "dist")));
app.listen(3000);

八.集成VueRouter

yarn add vue-router
import Vue from "vue";
import VueRouter from "vue-router";
import Foo from "./components/Foo.vue";
Vue.use(VueRouter);
export default () => {
  const router = new VueRouter({
    mode: "history",
    routes: [
      { path: "/", component: Foo },
      { path: "/bar", component: () => import("./components/Bar.vue") }
    ]
  });
  return router;
};

导出路由配置

配置入口文件

import Vue from "vue";
import App from "./App.vue";
import createRouter from "./router";
export default () => {
  const router = createRouter();
  const app = new Vue({
    router,
    render: h => h(App)
  });
  return { app, router };
};

配置组件信息

<template>
    <div id="app">
        <router-link to="/"> foo</router-link>
        <router-link to="/bar"> bar</router-link>
        <router-view></router-view>
    </div>
</template>

防止刷新页面不存在

router.get("*", async ctx => {
  ctx.body = await new Promise((resolve, reject) => {
    render.renderToString({ url: ctx.url }, (err, html) => {
      // 必须写成回调函数的方式否则样式不生效
      resolve(html);
    });
  });
});

保证异步路由加载完成

export default ({ url }) => {
  return new Promise((resolve, reject) => {
    const { app, router } = createApp();
    router.push(url);
    router.onReady(() => {
      const matchComponents = router.getMatchedComponents();
      if (!matchComponents.length) {
        return reject({ code: 404 });
      }
      resolve(app);
    }, reject);
  });
};

// 服务器可以监控到错误信息,返回404
render.renderToString({ url: ctx.url }, (err, html) => {
      // 必须写成回调函数的方式否则样式不生效
    if (err && err.code == 404) {
    resolve("404 Not Found");
    }
    resolve(html);
});

十.集成vuex配置

yarn add vuex
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

export default ()=>{
    let store = new Vuex.Store({
        state:{
            username:'song'
        },
        mutations:{
            changeName(state){
                state.username = 'hello';
            }
        },
        actions:{
            changeName({commit}){
                return new Promise((resolve,reject)=>{
                    setTimeout(() => {
                        commit('changeName');
                        resolve();
                    }, 1000);
                })
            }
        }
    });
    return store
}
// 引用vuex
import createRouter from './router';
import createStore from './store'
export default ()=>{
    let router = createRouter();
    let store = createStore();
    let app = new Vue({
        router,
        store,
        render:(h)=>h(App)
    })
    return {app,router,store}
}

在后端更新vuex

import createApp from './main';
export default (context)=>{
    return new Promise((resolve)=>{
        let {app,router,store} = createApp();
        router.push(context.url); // 默认访问到/a就跳转到/a
        router.onReady(()=>{
            let matchComponents = router.getMatchedComponents(); // 获取路由匹配到的组件
            
            Promise.all(matchComponents.map(component=>{
                if(component.asyncData){
                    return component.asyncData(store);
                }
            })).then(()=>{
                context.state = store.state; // 将store挂载在window.__INITIAL_STATE__
                resolve(app);

            });
        })
    })
}

在浏览器运行时替换store

// 在浏览器运行代码
if(typeof window !== 'undefined' && window.__INITIAL_STATE__){
    store.replaceState(window.__INITIAL_STATE__);
}

需要执行的钩子函数

export default {
 mounted() {
  return this.$store.dispatch("changeName");
 },
 asyncData(store) {
  return store.dispatch("changeName");
 }
};

以上就是Vue 服务端渲染SSR示例详解的详细内容,更多关于Vue 服务端渲染SSR的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Vue 服务端渲染SSR示例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Vue服务端如何渲染SSR

这篇文章主要介绍“Vue服务端如何渲染SSR”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue服务端如何渲染SSR”文章能帮助大家解决问题。手写Vue服务端渲染概念:放在浏览器进行就是浏览器渲染,
2023-07-02

Python实现服务端渲染SSR的示例代码

本文详细介绍了使用Python实现服务端渲染(SSR)的示例代码。SSR在服务器端渲染HTML响应,然后发送到客户端,具有更快的初始页面加载时间、更好的SEO和更丰富的用户体验。示例Python代码使用Flask和Jinja模板引擎,展示了如何创建根路由并渲染模板。优点包括:改善SEO更快的页面加载速度跨平台兼容性安全增强缺点包括:服务器负载延迟复杂性带宽消耗内容闪烁此代码示例仅供参考,对于复杂应用程序可能需要专门的SSR框架。
Python实现服务端渲染SSR的示例代码
2024-04-02

客户端渲染(CSR)与服务器端渲染(SSR)

在比较服务器端渲染(SSR)和客户端渲染(CSR)时,重要的是考虑性能的不同方面,

vue中如何实现SSR服务端渲染

本篇内容主要讲解“vue中如何实现SSR服务端渲染”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“vue中如何实现SSR服务端渲染”吧!一、SSR是什么Server-Side Rendering
2023-06-29

Vue组件的服务器端渲染(SSR)实现与优化

Vue组件服务器端渲染(SSR)通过在服务器端预渲染组件来提高页面加载速度,优化SEO和用户参与度。实现涉及安装SSR插件、创建服务器端渲染函数和配置web框架。为了优化,可以采用代码拆分、缓存、HTTP/2和预渲染策略等措施。SSR的好处包括更快的页面加载时间、更好的SEO和更高的用户参与度,但缺点包括服务器开销、对动态内容的限制和复杂性。最佳实践包括仅预渲染静态页面、优化性能、启用HTTP/2、实施渐进式增强并监控性能。
Vue组件的服务器端渲染(SSR)实现与优化
2024-04-02

一文探究Angular中的服务端渲染(SSR)

你知道 Angular Universal 吗?可以帮助网站提供更好的 SEO 支持哦!
2023-05-14

快速在你的Vue/React应用中实现SSR(服务端渲染)

服务端渲染(ssr),是指由服务器端完成页面的HTML 结构拼接,并且直接将拼接好的HTML发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的处理技术。

ReactQuery 渲染优化示例详解

这篇文章主要为大家介绍了ReactQuery 渲染优化示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

用 Nuxt.js 搭建一个服务端渲染(SSR)应用

Nuxt.js 是使用 Webpack 和 Node.js 进行封装的基于 Vue 的 SSR 框架,使用它,你可以不需要自己搭建一套 SSR 程序,而是通过其约定好的文件结构和API就可以实现一个首屏渲染的 Web 应用。

编程热搜

目录