Vue服务端如何渲染SSR

发布时间:2022-07-11 10:02:35 作者:iii
来源:亿速云 阅读:543

Vue服务端如何渲染SSR

目录

  1. 引言
  2. 什么是服务端渲染(SSR)
  3. Vue SSR 的优势
  4. Vue SSR 的基本原理
  5. Vue SSR 的实现步骤
  6. Vue SSR 的优化
  7. Vue SSR 的常见问题与解决方案
  8. Vue SSR 的实战案例
  9. 总结

引言

在现代Web开发中,单页应用(SPA)已经成为主流。然而,SPA在首次加载时通常需要下载大量的JavaScript文件,这会导致首屏加载时间较长,影响用户体验。为了解决这个问题,服务端渲染(SSR)应运而生。Vue.js 流行的前端框架,提供了强大的SSR支持。本文将详细介绍Vue服务端渲染的原理、实现步骤、优化技巧以及常见问题的解决方案。

什么是服务端渲染(SSR)

服务端渲染(Server-Side Rendering,简称SSR)是指在服务器端将Vue组件渲染成HTML字符串,然后将这些HTML字符串发送到客户端。客户端接收到HTML后,可以直接显示页面内容,而不需要等待JavaScript文件的下载和执行。

与传统的客户端渲染(CSR)相比,SSR具有以下优势:

Vue SSR 的优势

Vue.js 提供了官方的SSR支持,使得开发者可以轻松地将Vue应用转换为SSR应用。Vue SSR 的优势包括:

Vue SSR 的基本原理

Vue SSR 的基本原理是将Vue组件在服务器端渲染成HTML字符串,然后将这些HTML字符串发送到客户端。客户端接收到HTML后,Vue会在客户端重新激活这些组件,使其具有交互能力。

具体来说,Vue SSR 的工作流程如下:

  1. 服务器端渲染:服务器接收到客户端的请求后,根据请求的URL匹配相应的Vue组件,并将这些组件渲染成HTML字符串。
  2. 发送HTML到客户端:服务器将生成的HTML字符串发送到客户端,客户端接收到HTML后,可以直接显示页面内容。
  3. 客户端激活:在客户端,Vue会重新激活这些组件,使其具有交互能力。这个过程称为“客户端激活”。

Vue SSR 的实现步骤

5.1 安装依赖

首先,我们需要安装Vue SSR所需的依赖包。可以使用npm或yarn进行安装:

npm install vue vue-server-renderer express

5.2 创建 Vue 实例

接下来,我们需要创建一个Vue实例。这个实例将在服务器端和客户端共享。

// app.js
import Vue from 'vue';
import App from './App.vue';

export function createApp() {
  const app = new Vue({
    render: h => h(App)
  });

  return { app };
}

5.3 创建服务器

然后,我们需要创建一个Express服务器来处理客户端的请求,并将Vue组件渲染成HTML字符串。

// server.js
const express = require('express');
const { createApp } = require('./app');
const renderer = require('vue-server-renderer').createRenderer();

const server = express();

server.get('*', (req, res) => {
  const { app } = createApp();

  renderer.renderToString(app, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }

    res.end(`
      <!DOCTYPE html>
      <html lang="en">
        <head><title>Hello</title></head>
        <body>${html}</body>
      </html>
    `);
  });
});

server.listen(8080);

5.4 配置 Webpack

为了在服务器端和客户端共享Vue组件,我们需要配置Webpack。具体来说,我们需要为服务器端和客户端分别创建两个Webpack配置文件。

// webpack.server.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  target: 'node',
  externals: [nodeExternals()],
  entry: './app.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'server.bundle.js',
    libraryTarget: 'commonjs2'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
};
// webpack.client.config.js
const path = require('path');

module.exports = {
  entry: './entry-client.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'client.bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
};

5.5 处理路由

在SSR中,路由的处理方式与CSR有所不同。我们需要在服务器端根据请求的URL匹配相应的Vue组件。

// router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './components/Home.vue';
import About from './components/About.vue';

Vue.use(Router);

export function createRouter() {
  return new Router({
    mode: 'history',
    routes: [
      { path: '/', component: Home },
      { path: '/about', component: About }
    ]
  });
}
// app.js
import Vue from 'vue';
import App from './App.vue';
import { createRouter } from './router';

export function createApp() {
  const router = createRouter();

  const app = new Vue({
    router,
    render: h => h(App)
  });

  return { app, router };
}
// server.js
const express = require('express');
const { createApp } = require('./app');
const renderer = require('vue-server-renderer').createRenderer();

const server = express();

server.get('*', (req, res) => {
  const { app, router } = createApp();

  router.push(req.url);

  router.onReady(() => {
    renderer.renderToString(app, (err, html) => {
      if (err) {
        res.status(500).end('Internal Server Error');
        return;
      }

      res.end(`
        <!DOCTYPE html>
        <html lang="en">
          <head><title>Hello</title></head>
          <body>${html}</body>
        </html>
      `);
    });
  }, err => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }
  });
});

server.listen(8080);

5.6 数据预取

在SSR中,我们通常需要在服务器端预取数据,以便在渲染组件时使用这些数据。Vue SSR 提供了asyncData方法来实现数据预取。

// Home.vue
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ content }}</p>
  </div>
</template>

<script>
export default {
  asyncData({ store, route }) {
    return store.dispatch('fetchData', route.params.id);
  },
  computed: {
    title() {
      return this.$store.state.title;
    },
    content() {
      return this.$store.state.content;
    }
  }
};
</script>
// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export function createStore() {
  return new Vuex.Store({
    state: {
      title: '',
      content: ''
    },
    actions: {
      fetchData({ commit }, id) {
        return new Promise((resolve) => {
          setTimeout(() => {
            commit('setData', {
              title: 'Hello World',
              content: 'This is the content.'
            });
            resolve();
          }, 1000);
        });
      }
    },
    mutations: {
      setData(state, data) {
        state.title = data.title;
        state.content = data.content;
      }
    }
  });
}
// app.js
import Vue from 'vue';
import App from './App.vue';
import { createRouter } from './router';
import { createStore } from './store';

export function createApp() {
  const router = createRouter();
  const store = createStore();

  const app = new Vue({
    router,
    store,
    render: h => h(App)
  });

  return { app, router, store };
}
// server.js
const express = require('express');
const { createApp } = require('./app');
const renderer = require('vue-server-renderer').createRenderer();

const server = express();

server.get('*', (req, res) => {
  const { app, router, store } = createApp();

  router.push(req.url);

  router.onReady(() => {
    const matchedComponents = router.getMatchedComponents();

    if (!matchedComponents.length) {
      res.status(404).end('Not Found');
      return;
    }

    Promise.all(matchedComponents.map(component => {
      if (component.asyncData) {
        return component.asyncData({ store, route: router.currentRoute });
      }
    })).then(() => {
      renderer.renderToString(app, (err, html) => {
        if (err) {
          res.status(500).end('Internal Server Error');
          return;
        }

        res.end(`
          <!DOCTYPE html>
          <html lang="en">
            <head><title>Hello</title></head>
            <body>${html}</body>
          </html>
        `);
      });
    }).catch(err => {
      res.status(500).end('Internal Server Error');
    });
  }, err => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }
  });
});

server.listen(8080);

5.7 客户端激活

在客户端,我们需要重新激活Vue组件,使其具有交互能力。这个过程称为“客户端激活”。

// entry-client.js
import { createApp } from './app';

const { app, router, store } = createApp();

if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__);
}

router.onReady(() => {
  app.$mount('#app');
});
// server.js
const express = require('express');
const { createApp } = require('./app');
const renderer = require('vue-server-renderer').createRenderer();

const server = express();

server.get('*', (req, res) => {
  const { app, router, store } = createApp();

  router.push(req.url);

  router.onReady(() => {
    const matchedComponents = router.getMatchedComponents();

    if (!matchedComponents.length) {
      res.status(404).end('Not Found');
      return;
    }

    Promise.all(matchedComponents.map(component => {
      if (component.asyncData) {
        return component.asyncData({ store, route: router.currentRoute });
      }
    })).then(() => {
      const context = { state: store.state };

      renderer.renderToString(app, context, (err, html) => {
        if (err) {
          res.status(500).end('Internal Server Error');
          return;
        }

        res.end(`
          <!DOCTYPE html>
          <html lang="en">
            <head><title>Hello</title></head>
            <body>
              <div id="app">${html}</div>
              <script>window.__INITIAL_STATE__ = ${JSON.stringify(context.state)}</script>
              <script src="/dist/client.bundle.js"></script>
            </body>
          </html>
        `);
      });
    }).catch(err => {
      res.status(500).end('Internal Server Error');
    });
  }, err => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }
  });
});

server.listen(8080);

Vue SSR 的优化

6.1 缓存

为了提高SSR的性能,我们可以使用缓存机制。Vue SSR 提供了createBundleRenderer方法,可以将渲染结果缓存起来。

// server.js
const express = require('express');
const { createApp } = require('./app');
const renderer = require('vue-server-renderer').createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'), {
  cache: require('lru-cache')({
    max: 1000,
    maxAge: 1000 * 60 * 15
  })
});

const server = express();

server.get('*', (req, res) => {
  const context = { url: req.url };

  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }

    res.end(`
      <!DOCTYPE html>
      <html lang="en">
        <head><title>Hello</title></head>
        <body>${html}</body>
      </html>
    `);
  });
});

server.listen(8080);

6.2 代码分割

为了减少客户端加载的JavaScript文件大小,我们可以使用代码分割技术。Vue SSR 支持使用import()动态导入组件。

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

Vue.use(Router);

export function createRouter() {
  return new Router({
    mode: 'history',
    routes: [
      { path: '/', component: () => import('./components/Home.vue') },
      { path: '/about', component: () => import('./components/About.vue') }
    ]
  });
}

6.3 流式渲染

为了提高SSR的性能,我们可以使用流式渲染。Vue SSR 提供了renderToStream方法,可以将HTML以流的形式发送到客户端。

// server.js
const express = require('express');
const { createApp } = require('./app');
const renderer = require('vue-server-renderer').createRenderer();

const server = express();

server.get('*', (req, res) => {
  const { app, router } = createApp();

  router.push(req.url);

  router.onReady(() => {
    const stream = renderer.renderToStream(app);

    res.write(`
      <!DOCTYPE html>
      <html lang="en">
        <head><title>Hello</title></head>
        <body>
    `);

    stream.on('data', chunk => {
      res.write(chunk);
    });

    stream.on('end', () => {
      res.end(`
          </body>
        </html>
      `);
    });

    stream.on('error', err => {
      res.status(500).end('Internal Server Error');
    });
  }, err => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }
  });
});

server.listen(8080);

Vue SSR 的常见问题与解决方案

7.1 内存泄漏

在SSR中,由于每次请求都会创建一个新的Vue实例,如果不及时清理这些实例,可能会导致内存泄漏。为了解决这个问题,我们可以使用destroy方法手动销毁Vue实例。

”`javascript // server.js const express = require(‘express’); const { createApp } = require(‘./app’); const renderer = require(‘vue-server-renderer’).createRenderer();

const server = express();

server.get(‘*’, (req, res) => { const { app, router } = createApp();

router.push(req.url);

router.onReady(() => { renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end(‘Internal Server Error’); return; }

  res.end(`
    <!DOCTYPE html>
    <html lang="en">
      <head><title>Hello</title></head>
      <body>${html}</body>
    </html>
  `);

  app.$destroy();
});

}, err => { if (err) { res.status(500).end(‘Internal Server Error’); return; } }); });

server

推荐阅读:
  1. 怎么在vue中使用ssr+koa2构建服务端渲染
  2. Egg Vue SSR服务端渲染数据请求与asyncData的示例分析

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue ssr

上一篇:JS项目中如何对本地存储进行二次封装

下一篇:vue2中怎么使用tailwindcss方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》