基于Vue的SSR——传统SSR、nuxt.js实践、搭建自己的SSR

背景

现代化的前端框架(vue、react、angular等)构建的单页应用(SPA)具有用户体验好、渲染性能好、可维护性高等优点。但是同时也存在以下缺陷:

  1. 首屏加载时间长 ------ 单页应用使用 JavaScript 在客户端生成 HTML 来呈现内容,用户需要等待客户端 JS 解析执行完成才能看到页面,这就使得首屏加载时间变长,从而影响用户体验。
  2. 不利于SEO ------ 当搜索引擎爬取网站 HTML文件时,单页应用组件化开发使首页的HTML没有内容,因为他它需要通过客户端 JavaScript 解析执行才能生成网页内容。

现代化的服务端渲染提出了一种同构方式,汲取了客户端渲染和服务端渲染的优势来解决这种问题:

  • 通过服务端渲染首屏直出,解决首屏渲染慢以及不利于 SEO 问题
  • 通过客户端渲染接管页面内容交互得到更好的用户体验

1. 传统服务端渲染

早期Web页面渲染都是在服务端完成的,即服务端运行过程中将所需的数据结合页面模板渲染为 HTML,响应给客户端浏览器并直接展示,接下来我们来写一个 demo,来了解一下传统的服务端渲染:

arduino 复制代码
// node http服务
npm i express cors -D
// 服务端模板引擎
npm i art-template express-art-template -D

服务端代码:

js 复制代码
 const express = require('express');
 const cors = require('cors');
 const app = express();
 const template = require('art-template');
 const fs = require('fs');
 app.use(cors());
  
 const productObj = {
   code: 200,
   message: 'success',
   data: [
     {
       id: 1,
       title: 'Apple iPad Pro',
       desc:
         '苹果 Apple iPad Pro 11英寸平板电脑 2020年新款(256G WLAN版/全面屏/A12Z/Face ID/MXDC2CH/A) 深空灰色',
       price: 7029.9,
       thumb:
         'http://img14.360buyimg.com/n5/s54x54_jfs/t1/93232/3/15628/251953/5e723c47E54d3aff9/9d083eaea3409b83.jpg',
     },
     {
       id: 2,
       title: 'HUAWEI Mate 30 Pro 5G',
       desc:
         '华为 HUAWEI Mate 30 Pro 5G 麒麟990 OLED环幕屏双4000万徕卡电影四摄8GB+256GB丹霞橙5G全网通游戏手机',
       price: 6399.0,
       thumb:
         'http://img14.360buyimg.com/n5/s54x54_jfs/t1/90139/34/1379/180822/5dbe8875E02dc2e95/c78c80a4116ee57d.jpg',
     }
   ],
 };

 app.use(express.json());
 app.get('/getPage', (req, res) => {
   const templateStr = fs.readFileSync("./client.html", 'utf-8');
   const html = template.render(templateStr, productObj);
   res.send(html);
 });
 
 app.listen(3000, (val) => {
   console.log(`Server is running`);
 });

客户端代码:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        ul li {
            display: flex;
            flex-wrap: wrap;
            margin: 10px 10px 0 10px;
            padding: 16px;
            border-radius: 10px;
            background-color: #f5f5f5;
        }

        img {
            width: 100px;
            height: auto;
            margin-right: 10px;
        }

        P {
            padding: 10px 0;
        }
    </style>
</head>
<body>
    <ul>
        {{ each data }}
        <li>
            <img src="{{ $value.thumb }}" alt="" />
            <div>
                <p>{{ $value.title }}</p>
                <p>¥{{ $value.price }}</p>
                <p>
                    {{ $value.desc }}
                </p>
            </div>
        </li>
        {{ /each }}
    </ul>
</body>
</html>

运行测试: 本地启动node服务, 浏览器上输入地址:http://localhost:3000/getPage ,即可看见页面渲染成功: 现在看来,这种渲染模式是不合理的:

  1. 应用的前后端部分完全耦合在一起,在前后端协同开发方面会有非常大的阻力;
  2. 前端没有足够的发挥空间,无法充分利用现在前端生态下的一些更优秀的方案;
  3. 由于内容都是在服务端动态生成的,所以服务端的压力较大;相比目前流行的 SPA 应用来说,用户体验一般;

2. SSR应用的一站式解决方案 ------ nuxt.js

nuxtjs 简单来说就是一个vue的项目融合一个node.js server项目,这里node服务有两个作用:第一点是代替浏览器的工作,笼统理解就是在created 时的请求数据和页面渲染; 第二点是当作静态文件服务器,把渲染好的页面返回给用户

创建nuxt.js项目:

js 复制代码
// 打开自己定义好的项目目录文件,初始化node环境:
npm init -y

// 安装 nuxt
npm i nuxt -D

nuxt.js相关命令说明:

js 复制代码
nuxt ------ 启动一个热加载的 Web 服务器(开发模式) [localhost:3000](http://localhost:3000/)。
nuxt build ------ 利用 webpack 编译应用,压缩 JS 和 CSS 资源(发布用)。
nuxt start ------ 以生产模式启动一个 Web 服务器 (需要先执行`nuxt build`)。  
nuxt generate ------ 编译应用,并依据路由配置生成对应的 HTML 文件 (用于静态站点的部署)。  

`--spa` 或 `-s`:禁用服务器端渲染,使用 SPA 模式

nuxt.js项目的发布部署有以下方式:服务端渲染应用部署静态应用部署单页面应用程序部署 (SPA)服务端渲染应用部署是每次请求经过服务器,查询数据库或接口,渲染模板后返回html,算是动态渲染的; 静态应用部署是预先将所有路由页面处理后,生成静态的html,是一个完全不需要服务器的静态网站

  1. 部署Nuxt.js服务端渲染的应用,应该先进行编译构建,然后再启动 Nuxt 服务,可通过以下两个命令来完成:
sql 复制代码
nuxt build
nuxt start
  1. Nuxt.js可依据路由配置将应用静态化,使得我们可以将应用部署至任何一个静态站点主机服务商。可利用下面的命令生成应用的静态目录和文件。这个命令会创建一个 dist 文件夹,所有静态化后的资源文件均在其中。同时需要将nuxt.config.js中的target设置为static
arduino 复制代码
npm run generate
  1. 单页面应用程序部署 (SPA)使用时启用 SPA 模式 mode: 'spa'--spa,并且我们运行打包,生成在导报后自动启动,生成包含常见的 meta 和资源链接,但不包含页面内容。对于 SPA 部署,先将nuxt.config.js中的mode更改为spa。再执行以下命令,自动生成dist/文件夹。
js 复制代码
npm run build

2.1 nuxt.js本地实践

js 复制代码
{
  "name": "nuxt-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nuxt",              
    "spa": "nuxt --spa",      
    "generate": "nuxt generate",
    "build": "nuxt build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "nuxt": "^2.15.8"
  }
}

本地nuxt.js项目简单构建好之后,我们将nuxt的相关执行命令集成在在package.json文件的scripts中,通过终端的npm run xxx来执行。在根目录下新建pages文件夹,Nuxt.js 依据 pages 目录中的所有 *.vue 文件生成应用的路由配置。执行 npm run dev,Nuxt.js会根据 pages 目录结构自动生成 vue-router 模块的路由配置。

本地访问:http://localhost:3000/ 即index.vue首页展示内容。本文通过nuxt.js搭建简单的SSR项目,更多内容可以参考 nuxt.js中文官网

3. 搭建自己的SSR服务

笔者将通过vue的简单应用 和 vue的工程化开发的demo来展开对SSR服务应用的理解。

3.1 基于vue的SSR简单应用

首先我们是实现一个简单的WEB服务中渲染Vue的实例:vue-server-renderer是VUE官方提供的服务端渲染的插件,此处和express作为服务端集成在一起使用,可以做到简单的服务端的渲染。

js 复制代码
const Vue = require('vue');
const service = require('express')();

// 创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer();

service.get('/', (req, res) => {
  // 创建一个 Vue 实例
  const app = new Vue({
    template: `
      <div>{{ message }}</div>`,
    data: {
      message: 'Hello World',
    },
  });

  // 将 Vue 实例渲染为 HTML
  renderer.renderToString(app, (err, html) => {
    // 异常时,抛500,返回错误信息,并阻止向下执行
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }

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

// 绑定并监听指定主机和端口上的连接
service.listen(3000);

当渲染 Vue 应用程序时,renderer 可以从应用程序生成 HTML 标记。为了简化这些,可以直接在创建 renderer 时提供一个页面模板。多数时候,我们会将页面模板放在特有的文件中。其中<!--vue-ssr-outlet--> 注释在这里起到一个标记作用,将应用程序html 在此标记处处替换,渲染成html页面。

js 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>
js 复制代码
const Vue = require('vue');
const service = require('express')();

// 创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer({
  template: require('fs').readFileSync('./index.template.html', 'utf-8'),
});
const context = {
  title: 'vue ssr demo',
  metas: `
        <meta name="keyword" content="vue,ssr">
        <meta name="description" content="vue srr demo">
    `,
};
 
service.get('/', (req, res) => {
  // 设置响应头,解决中文乱码
  res.setHeader('Content-Type', 'text/html;charset=utf8');

  // 创建一个 Vue 实例
  const app = new Vue({
    template: `
      <div>{{ message }}</div>`,
    data: {
      message: 'Hello World',
    },
  });
 
  // 将 Vue 实例渲染为 HTML
  renderer.renderToString(app, context, (err, html) => {
    // 异常时,抛500,返回错误信息,并阻止向下执行
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }
    // 返回HTML, 该html的值 将是注入应用程序内容的完整页面
    res.end(html);
  });
 });
 
 // 绑定并监听指定主机和端口上的连接
 service.listen(3000);

然而在实际项目中,不止上述例子那么简单,需要考虑很多方面:路由、数据预取、组件化、全局状态等,所以服务端渲染不是只用一个简单的模板,然后加上使用vue-server-renderer完成。完整的依赖 vue-server-renderer做服务端渲染的详细流程和步骤可以参考文章 Vue服务端渲染实践------Web应用首屏耗时最优化方案

相关推荐
天天进步20151 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员2 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
想自律的露西西★3 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
白墨阳3 小时前
vue3:瀑布流
前端·javascript·vue.js
程序媛-徐师姐4 小时前
Java 基于SpringBoot+vue框架的老年医疗保健网站
java·vue.js·spring boot·老年医疗保健·老年 医疗保健
余道各努力,千里自同风5 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave5 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟5 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾5 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js