SPA

简介

单页Web应用(SPA - Single Page web Application)
也就是说只有一个HTML文件的Web应用, 我们就称之为单页Web应用, 就称之为SPA应用
我们通过Vue开发的项目其实就是典型的SPA应用

特点

1)SPA应用只有一个HTML文件, 所有的内容其实都在这个页面中呈现的
2)SPA应用只会加载一次HTML文件, 不会因为用户的操作而进行页面的重新加载
当用户与应用程序交互时, 是通过动态更新页面内容的方式来呈现不同的内容

优点

1) 有良好的交互体验 - 不会重新加载整个网页, 只是局部更新
2) 前后端分离开发 - 前端负责页面呈现和交互, 后端负责数据
3) 减轻服务器压力 - 只用处理数据不用处理界面
4) 共用一套后端程序代码

缺点

1) SEO难度较高
只有一个界面, 无法针对不同的内容编写不同的SEO信息
2) 初次加载耗时多
为实现单页Web应用功能及显示效果,需要在加载页面的时候将所有JavaScript、CSS统一加载,
在Vue中可以使用按需加载解决

SEO

SEO三大标签

  1. <title>网易云音乐</title>
  2. <!--"keywords"它为文档定义了一组关键字。搜索引擎在遇到这些关键字时,会用这些关键字对文档进行分类。 -->
  3. <meta name="keywords" content="网易云音乐,音乐,播放器,网易,下载,播放,DJ,免费,明星,精选,歌单,识别音乐,收藏,分享音乐,音乐互动,高音质,320K,音乐社交,官网,移动站,music.163.com">
  4. <!--description用来告诉搜索引擎你的网站主要内容。-->
  5. <meta name="description" content="网易云音乐是一款专注于发现与分享的音乐产品,依托专业音乐人、DJ、好友推荐及社交功能,为用户打造全新的音乐生活。">

常见的三种网页渲染方式

1.1客户端渲染(CSR - Client Side Render)
后端只提供数据,前端做视图和交互逻辑(SPA应用就是典型的客户端渲染)
1.2客户端渲染过程
1. 客户端请求html (请求)
2. 服务端返回html
3. 客户端渲染HTML,找到依赖的JS/CSS文件
3. 客户端请求对应的JS/CSS文件 (请求)
4. 服务端返回对应的JS/CSS文件
5. 客户端等待JS/CSS文件下载完成
6. 客户端加载并初始化下载好的JS文件
7. 客户端执行JS代码向后端请求数据 (请求)
8. 服务端返回数据
9. 客户端利用服务端返回的数据填充网页
最大优点: 交互体验好可以做局部更新
最大缺点: 首屏加载慢(因为要等到HTML下载完才会去下载JS/CSS, 要等到JS下载完初始化完才会去获取数据),
—-
1.1服务端渲染(SSR - Server Side Render)
后端既提供数据又提供视图和交互逻辑
也就是服务器接到客户端请求之后,找到对应的数据并根据找到的数据生成对应的视图
然后将包含数据的视图一次性发给客户端,客户端直接将渲染即可
1.2服务端渲染过程
1.客户端请求html (请求)
2.服务端内部查找对应的html文件和数据
3.服务器内部根据数据html文件和数据生成完整网页
4.服务端返回完整网页
5.客户端渲染HTML,找到依赖的JS/CSS文件
5.客户端请求对应的JS/CSS文件 (请求)
6.客户端等待JS/CSS文件下载完成
7.客户直接展示网页
最大优点: 首屏加载快(因为服务器返回的网页已经包含数据, 所以之下载完JS/CSS就可以直接渲染)
每次请求返回的都是一个独立完成的网页, 更利于SEO
最大缺点:网络传输数据量大
—-
1.1预渲染
无需服务器实时动态编译,采用预渲染,在构建时针对特定路由简单的生成静态HTML文件
本质就是客户端渲染, 只不过和SPA不同的是预渲染有多个界面
最大优点: 由于有多个界面, 所以更利于SEO
最大缺点: 首屏加载慢, 预编译会非常的慢
1.4如何选择
1.注重SEO的新闻、电商网站,建议采用服务器端渲染
2.强交互的页面,不注重SEO,采用客户端渲染
3.两者之间, 只需改善少数页面的SEO,采用预渲染

预渲染解决SPA应用SEO问题

指定生成html文件,解决单页面问题

针对特定路由简单的生成静态HTML文件
https://www.npmjs.com/package/vue-cli-plugin-prerender-spa
注意点: Router必须使用history模式

  1. 安装:终端vue add prerender-spa ->都选y
  2. router修改为history

    // 注意点: 如果需要使用预渲染的插件, 那么Router的模式必须是history模式
    mode: 'history',
    
  3. vue.config.js 修改需要生成html页面的router

    pluginOptions: {
     prerenderSpa: {
       registry: undefined,
       renderRoutes: [
         '/',
         '/recommend',
         '/singer',
         '/rank',
         '/search',
         '/account',
         '/detail'
       ],
       useRenderEvent: true,
       headless: true,
       onlyProduction: true
     }
    }
    
  4. npm run build 生成对应html文件

image.png

  1. 将app.html 修改为index.html

动态为每个hml配置SEO三大标签

经过上边虽然可以生成多个html文件,但并没有设置SEO三大标签

  1. npm install vue-meta-info —save
  2. 配置 https://www.npmjs.com/package/vue-meta-info
  3. 新增文件vue-meta-info.js来统一配置各文件SEO标签 ```json export default { recommend: { title: ‘我是recommend’, meta: [ {
     name: 'keyWords',
     content: '关键字1,关键字1,关键字1'
    
    }, {
     name: 'description',
     content: '这是一段网页的描述1'
    
    } ] }, singer: { title: ‘我是singer’, meta: [ {
     name: 'keyWords',
     content: '关键字2,关键字2,关键字2'
    
    }, {
     name: 'description',
     content: '这是一段网页的描述2'
    
    } ] } }

4. 各组件引入  MetaInfo  导出 metaInfo

![image.png](https://cdn.nlark.com/yuque/0/2021/png/1624878/1633250547184-29a34a64-4d48-4252-a40d-39748825ddaa.png#clientId=ua2b3becb-0f9e-4&from=paste&height=152&id=ua21b314e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=247&originWidth=667&originalType=binary&ratio=1&size=37907&status=done&style=none&taskId=ue8566351-cd5f-481a-a9d3-b05b4481772&width=409.5)

5. 实现切换页面时动态修改SEO

![image.png](https://cdn.nlark.com/yuque/0/2021/png/1624878/1633183645307-9f12c392-225b-41e7-a80b-67525b1bf0ca.png#clientId=ua23f015b-c2f9-4&from=paste&height=35&id=ub3ec0c81&margin=%5Bobject%20Object%5D&name=image.png&originHeight=70&originWidth=1036&originalType=binary&ratio=1&size=24338&status=done&style=none&taskId=uf95ade7e-320d-42a9-985a-1fa79c56bc3&width=518)
<a name="ZkEii"></a>
### 预渲染修改像素比问题
<a name="B5S9J"></a>
#### 做项目时动态添加了视口标签
```json
//window.devicePixelRatio告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素 返回1|2|3|?
let scale = 1.0 / window.devicePixelRatio;
//设置视口标签
let text =
`<meta name="viewport" content="width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no">`;
document.write(text);

什么是Viewport
手机浏览器是把页面放在一个虚拟的“窗口”(viewport)中,通常这个虚拟的“窗口”(viewport)比屏幕宽,这样就不用把每个网页挤到很小的窗口中(这样会破坏没有针对手机浏览器优化的网页的布局),用户可以通过平移和缩放来看网页的不同部分。移动版的 Safari 浏览器最新引进了 viewport 这个 meta tag,让网页开发者来控制 viewport 的大小和缩放,其他手机浏览器也基本支持。

Viewport 基础
一个常用的针对移动网页优化过的页面的 viewport meta 标签大致如下:

width:控制 viewport 的大小,可以指定的一个值,如果 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。
height:和 width 相对应,指定高度。
initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。
maximum-scale:允许用户缩放到的最大比例。
minimum-scale:允许用户缩放到的最小比例。
user-scalable:用户是否可以手动缩放

去除多余的预渲染添加的meta标签

原因
预渲染的本质就是在打包时就”模拟浏览器”提前访问路由对应的网页, 然后将访问的结果写入到.html文件中
但是由于在打包时访问并不是通过”真实的浏览器”来访问, 所以拿不到正是的像素比所以预渲染时写入到.html文件中的meta就是错误的
也正是因为预渲染的时候已经写入过meta标签了, 而运行的时候又会执行一次JS代码再写入一次就导致了网页中有两个meta标签,应该去除掉默认的 留下自己的
下是预渲染添加的视口标签

解决
vue.config.js

  pluginOptions: {
    prerenderSpa: {
      registry: undefined,
      renderRoutes: [
        '/',
        '/recommend',
        '/singer',
        '/rank',
        '/search',
        '/account',
        '/detail'
      ],
      useRenderEvent: true,
      headless: true,
      onlyProduction: true,
      postProcess: route => {
        //参数route是一个对象,route.html是该页面的html字符串
        // 预渲染内容写入之前的额外操作

        //去除预渲染默认添加的meta标签
        let reg = /<meta name="viewport".*user-scalable=no">/gi
        let res = route.html.match(reg)
        route.html = route.html.replace(res[1], '')

        return route
      }
    }
  }

去除预渲染添加的Loading…

原因
与上边一样 预渲染添加的 如下图,此时页面存在Loading不消失
image.png
去除生成的html文件中的Loading,此处使用正则不合适,可以利用类选择器去除该元素
image.png
由于vue.config.js是nodejs代码在node环境运行,没有doucument借助jsdom将字符串转换成document对象。
解决
vue.config.js

  pluginOptions: {
    prerenderSpa: {
      registry: undefined,
      renderRoutes: [
        '/',
        '/recommend',
        '/singer',
        '/rank',
        '/search',
        '/account',
        '/detail'
      ],
      useRenderEvent: true,
      headless: true,
      onlyProduction: true,
      postProcess: route => {
        //参数route是一个对象,route.html是该页面的html字符串
        // 预渲染内容写入之前的额外操作

        // 1.根据字符串创建一个网页
        let html = new JSDOM(route.html)
        // 2.从创建好的网页中拿到document对象
        let dom = html.window.document
        // 3.找到对应的元素, 删除对应的元素
        let loadingEle = dom.querySelector('.container')
        dom.body.removeChild(loadingEle)

        route.html = html.serialize()

        return route
      }
    }
  }