Vue的SSR介绍

这篇具有很好参考价值的文章主要介绍了Vue的SSR介绍。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

      与传统 SPA 相比,服务器端渲染 (SSR) 对SEO更加友好,方便搜索引擎爬虫抓取,可以直接查看完全渲染的页面,除此之外,SSR能够在更短的时间内渲染出页面内容,让用户有更好的用户体验。

前言

本文将从以下三个模块介绍服务端渲染:

  • 什么是客户端渲染?
  • 什么是服务端渲染?
  • 如何实现服务端渲染?希望看完后对你有所帮助!

客户端渲染

ssr,vue.js,javascript,前端

1、概念

      客户端渲染(CSR),即传统的单页面应用(SPA)模式,Vue构建的应用程序默认情况下是一个HTML模板页面,只有一个id为app的根容器,然后通过webpack打包生成css、js等资源文件,浏览器加载、解析来渲染HTML。

      右键查看一个标准的Vue项目网页源代码,可以看出源代码并没有页面中实际渲染的相关内容,只有一个id为app的根元素,所以说网页展示出来的内容是通过 JavaScript 动态渲染出来的。这种通过浏览器端的 JavaScript 为主导来渲染网页的方式就是客户端渲染

ssr,vue.js,javascript,前端

2、优缺点

2.1、优点:

前后端分离;体验更好;

2.2、缺点:

首屏渲染慢;SEO不友好;

服务端渲染

      服务端渲染(Server Side Render )就是将一个Vue组件在服务器端渲染为HTML字符串并发送到浏览器,最后将这些静态标记“激活”为可交互应用程序的过程称为服务端渲染。

      简言之,在浏览器请求页面URL的时候,服务端将我们需要的HTML文本组装好,并返回给浏览器,这个HTML文本被浏览器解析之后,不需要经过 JavaScript 脚本的执行,即可直接构建出希望的 DOM 树并展示到页面中。这个服务端组装HTML的过程,叫做服务端渲染。

实现

1、小试牛刀

      我们先简单实现服务端渲染,通过Vue3自带的 server-renderer 异步生成我们需要的HTML代码。

// nodejs服务器  express koa
const express = require('express');
const { createSSRApp } = require('vue');
const { renderToString } = require('@vue/server-renderer');

// 创建express实例
let app = express();

// 通过渲染器渲染page可以得到html内容
const page = createSSRApp({
    data: () => {
        return {
            title: 'ssr',
            count: 0,
        }
    },
    template: `<div><h1>{{title}}</h1>hello world!</div>`,
});

app.get('/', async function (req, res) {
    try {
        // 异步生成html
        const html = await renderToString(page);
        res.send(html);
    } catch (error) {
        res.status(500).send('系统错误');
    }
});

app.listen(9001, () => {
    console.log('9001 success');
});

然后通过 node 命令启动,也可以通过 nodemon 启动(关于 nodemon 使用大家可以自行百度)。

node .\server\index.js
// or
nodemon .\server\index.js

ssr,vue.js,javascript,前端


node启动
ssr,vue.js,javascript,前端

然后打开 http://localhost:9001/ 就可以看到:

ssr,vue.js,javascript,前端


右击查看网页源代码后:
ssr,vue.js,javascript,前端


      从网页源代码可以看出,当浏览器从服务端直接拿到HTML代码后,不需要执行 JavaScript 代码也可以将 **hello world!** 显示在页面上。这就是简单实现SSR的全部过程了。

      大家可以用 vue-cli 新建一个vue项目,在页面显示 hello world ,然后通过查看网页源代码对比区别!

2、同构项目

      前面已经通过简单的案例来演示了SSR,那么如何应用在我们的项目中呢?这里需要引入一个概念:同构。所谓同构,就是让一份代码,既可以在服务端中执行,也可以在客户端中执行,并且执行的效果都是一样的,都是完成这个 HTML 的组装,正确的显示页面。也就是说,一份代码,既可以客户端渲染,也可以服务端渲染。

ssr,vue.js,javascript,前端

2.1、服务端、客户端配置文件

      在根目录新建 webpack 目录,此文件夹主要存放打包配置文件。
新建服务端配置文件:server.config.js

const base = require('./base.config.js');
const path = require('path');
// webpack插件
const { default: merge } = require('webpack-merge');
const nodeExternals = require("webpack-node-externals");
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');

module.exports = merge(base, {
    mode: "production",
    // 将 entry 指向应用程序的 server 文件
    entry: {
        'server': path.resolve(__dirname, '../entry/server.entry.js')
    },
    // 对 bundle renderer 提供 source map 支持
    devtool: 'source-map',
    // 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的 bundle 文件。
    externals: nodeExternals({
        allowlist: [/\.css$/],
    }),
    output: {
        path: path.resolve(__dirname, './../dist/server'),
        filename: '[name].server.bundle.js',
        library: {
            type: 'commonjs2'   // 构建目标加载模式 commonjs
        }
    },
    // 这允许 webpack 以 Node 适用方式处理动态导入
    // 并且还会在编译 Vue 组件时告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
    target: 'node',
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                        plugins: [["@babel/plugin-transform-runtime", {
                            "corejs": 3
                        }]]
                    },

                },
                exclude: /node_modules/
            }
        ]
    },
    // 这是将服务器的整个输出构建为单个 JSON 文件的插件。
    // 服务端默认文件名为 `ssr-manifest.json`
    plugins: [
        new WebpackManifestPlugin({ fileName: 'ssr-manifest.json' }),
    ],
})

新建客户端配置文件:client.config.js

const base = require('./base.config.js');
const path = require('path');
// webpack插件
const { default: merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");

module.exports = merge(base, {
    mode: "production",
    // 对 bundle renderer 提供 source map 支持
    devtool: 'source-map',
    // 将 entry 指向应用程序的 client 文件
    entry: {
        'client': path.resolve(__dirname, '../entry/client.entry.js')
    },
    output: {
        path: path.resolve(__dirname, './../dist/client'),
        clean: true,
        filename: '[name].client.bundle.js',
    },
    plugins: [
        // 通过 html-webpack-plugin 生成client index.html
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: path.resolve('public/index.html')
        }),
        // 图标
        new CopyPlugin({
            patterns: [
                { from: path.resolve(__dirname, "../public/favicon.ico"), to: path.resolve(__dirname, './../dist/client') },
            ],
        }),
    ]
})

最后新建 base 配置文件:base.config.js

const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
    // 输出
    output: {
        path: path.resolve(__dirname, './../dist'),
        filename: '[name].bundle.js',
    },
    //  loader
    module: {
        rules: [
            { test: /\.vue$/, use: 'vue-loader' },
            {
                test: /\.css$/, use: [
                    'vue-style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    "vue-style-loader",
                    "css-loader",
                    "sass-loader",
                ],
            },
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    },

                },
                exclude: /node_modules/
            }
        ],
    },
    plugins: [
        new VueLoaderPlugin(),
    ]
}

到此就完成了配置文件,最后贴出打包命令(package.json)

{
  "scripts": {
    "build:client": "webpack --config ./webpack/client.config.js",
    "build:server": "webpack --config ./webpack/server.config.js",
    "build": "npm run build:client && npm run build:server"
  },
}

2.2、入口(entry)文件

在根目录下创建 entry 目录,此文件夹主要存放入口文件。
新建通用入口文件:app.js

// 通用入口
// 创建VUE实例
import { createSSRApp } from 'vue';
import App from './../src/App.vue';


export default function () {
    return createSSRApp(App);
}

新建服务端入口文件:server.entry.js

import createApp from './app';
// 服务器端路由与客户端使用不同的历史记录
import { createMemoryHistory } from 'vue-router'
import createRouter from './router.js'
import createStore from './store';
import { renderToString } from '@vue/server-renderer'

export default context => {
    return new Promise(async (resolve, reject) => {
        const app = createApp();
        const router = createRouter(createMemoryHistory())
        const store = createStore();
        app.use(router);
        app.use(store);

        // 设置服务器端 router 的位置
        await router.push(context.url);
        // isReady 等到 router 将可能的异步组件和钩子函数解析完
        await router.isReady();
        // 匹配路由是否存在
        const matchedComponents = router.currentRoute.value.matched.flatMap(record => Object.values(record.components))
        // 不存在路由,返回 404
        if (!matchedComponents.length) {
            return reject({ code: 404 });
        }
        // 对所有匹配的路由组件调用 `asyncData()`
        Promise.all(matchedComponents.map(component => {
            if (component.asyncData) {
                return component.asyncData({
                    store,
                    route: router.currentRoute.value
                });
            }
        })).then(async (res) => {
            let html = await renderToString(app);

            html += `<script>window.__INITIAL_STATE__ = ${replaceHtmlTag(JSON.stringify(store.state))}</script>`

            resolve(html);
        }).catch(() => {
            reject(html)
        })
    })
}

/**
 * 替换标签
 * @param {*} html 
 * @returns 
 */
function replaceHtmlTag(html) {
    return html.replace(/<script(.*?)>/gi, '&lt;script$1&gt;').replace(/<\/script>/g, '&lt;/script&gt;')
}

新建客户端入口文件:client.entry.js

// 挂载、激活app
import createApp from './app';
import { createWebHistory } from 'vue-router'
import createRouter from './router.js'
import createStore from './store';
const router = createRouter(createWebHistory())

const app = createApp();
app.use(router);
const store = createStore();

// 判断window.__INITIAL_STATE__是否存在,存在的替换store的值
if (window.__INITIAL_STATE__) {
    // 激活状态数据
    store.replaceState(window.__INITIAL_STATE__);
}
app.use(store)

// 在客户端和服务端我们都需要等待路由器先解析异步路由组件以合理地调用组件内的钩子。因此使用 router.isReady 方法
router.isReady().then(() => {
    app.mount('#app')
})

服务端、客户端入口文件完成后,需要对路由(router)和 store 进行共享。
新建router.js

import { createRouter } from 'vue-router'

const routes = [
    { path: '/', component: () => import('../src/views/index.vue') },
    { path: '/about', component: () => import('../src/views/about.vue') },
]

// 导出路由仓库
export default function (history) {
    // 工厂
    return createRouter({
        history,
        routes
    })
}

还需要两个vue组件,分别是index.vue、about.vue

<script>
    // 声明额外的选项
    export default {
        // 对外暴露方法,执行store
        asyncData: ({ store, route }) => {
            // 触发 action 后,会返回 Promise
            return store.dispatch('asyncSetData', route.query?.id || route.params?.id);
        },
    }
</script>
<script setup>
    import { computed } from 'vue';
    import { useStore } from 'vuex';
    import {generateRandomInteger} from '../utils'

    const clickMe = () => {
        store.dispatch('asyncSetData', generateRandomInteger(1,4));
    }

    const store = useStore();
    // 得到后赋值
    const storeData = computed(() => store.state.data);

</script>
<template>
    <div>
        <div>index</div>
        <button @click="clickMe">点击</button>
        <div style="margin-top:20px">store
            <div>id: {{ storeData?.id }}</div>
            <div>title: {{ storeData?.title }}</div>
        </div>
    </div>
</template>

<style lang='scss' scoped>
</style>
<script setup>
    import { ref } from 'vue'
</script>
<template>
    <div>about</div>
</template>

<style lang='scss' scoped>
</style>

为了方便测试,我们将App.vue修改为router-view

<template>
    <div id="nav">
        <router-link to="/?id=1">Home</router-link> |
        <router-link to="/about">About</router-link>
    </div>
    <router-view />
</template>

<style lang="scss">
#app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
}

#nav {
    padding: 30px;

    a {
        font-weight: bold;
        color: #2c3e50;

        &.router-link-exact-active {
            color: #42b983;
        }
    }
}
</style>

新建store.js

// 实例化store
import { createStore as _createStore } from 'vuex';
// 引入数据
import { getData } from './data';

// 对外导出一个仓库
export default function createStore() {
    return _createStore({
        state: {
            // 状态数据
            data: {}
        },
        mutations: {
            // 同步数据
            SET_DATA(state, item) {
                state.data = item;
            }
        },
        actions: {
            // 异步数据
            asyncSetData({ commit }, id) {
                getData(id).then(item => {
                    commit('SET_DATA', item);
                })
            },
        },
        modules: {}
    });
}

为了方便测试,我们新建一个data.js文件

export function getData(id) {
    const bookList = [
        { id: 1, title: '西游记' },
        { id: 2, title: '红楼梦' },
        { id: 3, title: '水浒传' },
        { id: 4, title: '三国演义' },
    ];

    const item = bookList.find(i => i.id == id);
    return Promise.resolve(item);
}

      到这里我们就可以构建客户端、服务端了,命令为 npm run build,这里的构建会自动构建两次,分别是 build:client、build:server,当终端出现 successfully 时则表示构建成功。

ssr,vue.js,javascript,前端

2.3、server.js

最后就是修改server/index.js文件,为了区别在此新建index2.js

const express = require('express')
const { renderToString } = require('@vue/server-renderer')
const app = express();
const path = require('path');
// 构建结果清单
const manifest = require('./../dist/server/ssr-manifest.json')
const appPath = path.join(__dirname, './../dist/server', manifest['server.js'])
const createApp = require(appPath).default;
const fs = require('fs');

// 搭建静态资源目录
// 这里index必须为false,有兴趣的话可以试试前后会有什么区别
app.use('/', express.static(path.join(__dirname, '../dist/client'), { index: false }));

// 获取模板
const indexTemplate = fs.readFileSync(path.join(__dirname, './../dist/client/index.html'), 'utf-8');

// 匹配所有的路径,搭建服务
app.get('*', async (req, res) => {
    try {
        const appContent = await createApp(req);

        const html = indexTemplate
            .toString()
            .replace('<div id="app">', `<div id="app">${appContent}`)

        res.setHeader('Content-Type', 'text/html');
        res.send(html);
    } catch (error) {
        console.log('error', error)
        res.status(500).send('服务器错误');
    }

})

app.listen(9002, () => {
    console.log('success 9002')
});

新建模板文件:index.html

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>vue ssr</title>
    </head>

    <body>
        <div id="app"></div>
    </body>

</html>

然后执行 nodemon .\server\index2.js,访问http://localhost:9002/?id=1

ssr,vue.js,javascript,前端

查看网页源代码

ssr,vue.js,javascript,前端

总结

      当浏览器访问服务端渲染项目时,服务端将 URL 传给到预选构建好的 VUE 应用渲染器,渲染器匹配到对应的路由的组件之后,执行我们预先在组件内定义的 asyncData 方法获取数据,并将获取完的数据传递给渲染器的上下文,利用 template 组装成HTML,并将 HTML 和状态 state 一并 send 给浏览器,浏览器加载了构建好的 Vue 应用后,将 state 数据同步到前端的 store 中,并根据数据激活后端返回的被浏览器解析为 DOM 元素的HTML文本,完成了数据状态、路由、组件的同步,使得白屏时间更短,有了更好的加载体验,同时更有利于 SEO 。文章来源地址https://www.toymoban.com/news/detail-744103.html

参考资料

  • Server-Side Rendering (SSR)
  • 从头开始,彻底理解服务端渲染原理(8千字汇总长文)
  • 彻底理解服务端渲染 - SSR原理

到了这里,关于Vue的SSR介绍的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue服务端渲染SSR

    一:ssr的理解 1、服务端渲染 Server Side Render SSR解决方案,后端渲染出完整的首屏的dom结构返回,前端拿到的内容包括首屏及完整spa结构,应用激活后依然按照spa方式运行,这种页面渲染方式被称为服务端渲染 (server side render) 二:Vue SSR实战 1、新建工程 vue-cli创建工程即可 2、

    2024年02月10日
    浏览(42)
  • Vue面试之csr与ssr渲染的区别

        最近在整理一些前端面试中经常被问到的问题,分为vue相关、react相关、js相关、react相关等等专题,可持续关注后续内容,会不断进行整理~ CSR(Client-Side Rendering)和SSR(Server-Side Rendering)是两种不同的前端渲染方式,它们在页面加载和渲染的过程中有一些显著的区别。

    2024年02月01日
    浏览(43)
  • 在Vue 3中如何实现服务端渲染(SSR)

    今天我要给你们介绍一个很酷的功能——在Vue 3中实现服务端渲染(SSR) 首先,我们来聊聊SSR是什么。它就像是一个魔术师,能让你的网页在服务器上就预先渲染好,然后发送到客户端。想象一下,你在浏览一个网页,一点开链接,页面就直接出现在你面前,就像变魔术一样

    2024年02月13日
    浏览(44)
  • Vue3+Vite使用Puppeteer进行SEO优化(SSR+Meta)

    【笑小枫】https://www.xiaoxiaofeng.com上线啦 资源持续整合中,程序员必备网站,快点前往围观吧~ 我的个人博客【笑小枫】又一次版本大升级,虽然知道没有多少访问量,但我还是整天没事瞎折腾。因为一些功能在Halo上不太好实现,所以又切回了Vue3项目,本文就是对于Vue单页面

    2024年01月25日
    浏览(55)
  • 前端ssr跟ssg的区别

    前端渲染方案SSR / SSG 前端SSR(Server-side Rendering)与SSG(Static Site Generation)是两种不同的技术,用于提高网站性能和用户体验。 SSR:服务端渲染 Server Side Render,PHP / Java / Python 后台基本能力,生成 HTML 模板,交由浏览器渲染。 SSG:页面静态化 Static Side Generation,把 node 提前渲染成

    2024年02月09日
    浏览(53)
  • 个人建站前端篇(二)项目采用服务端渲染SSR

    更好的SEO 首屏加载速度更快,用户体验更好 可以使用相同的语言以及相同的声明式、面向组件的心智模型来开发整个应用,而不需要在后端模板系统和前端框架之间来回切换。 Nuxt是一个构建于 Vue 生态系统之上的全栈框架 Quasar是基于 Vue 的完整解决方案 Vite 提供了内置的

    2024年02月19日
    浏览(47)
  • 《前端与SEO》—— 第五章:SPA与SSR 对 SEO 的影响

    由于MVVM开发模式的兴起,很多网站的采用 SPA模式进行开发。尽管SPA模式有着诸多好处,但由于其特性,在SEO方面不太理想。 先对 SPA做个简单了解。 什么是SPA? 单页Web应用(single page web application,SPA),就是只有一张Web页面的应用。单页应用程序 (SPA) 是加载单个HTML 页面并在

    2024年02月09日
    浏览(41)
  • Angular SSR 探究

    一般来说,普通的 Angular 应用是在 浏览器 中运行,在 DOM 中对页面进行渲染,并与用户进行交互。而 Angular Universal 是在 服务端 进行渲染(Server-Side Rendering,SSR),生成静态的应用程序网页,然后在客户端展示,好处是可以更快地进行渲染,在提供完整的交互之前就可以为用

    2024年01月20日
    浏览(39)
  • SSR使用HTTPS

    1.安装  npm i browser-sync 2. 再angular.json里配置 3.server.ts 修改function run()

    2024年02月12日
    浏览(37)
  • 模块四(一):搭建自己的SSR

    前言:同构渲染是将服务器渲染和客户端渲染相结合的一种渲染方式,在服务端生成初始页面,提升首屏加载速度,并且有利于SEO;在客户端接管HTML,并且将静态HTML激活为数据绑定的动态HTML,为用户提供更流畅的交互服务,并且可以更流畅的实现路由跳转,无需刷新整个页

    2024年02月04日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包