服务端渲染基础

一、概述

SPA单页面应用优点

  • 用户体验好
  • 开发效率高
  • 渲染性能好
  • 可维护性好

SPA单页面应用缺点

  • 首屏渲染时间长
  • 不利于SEO

最早期,Web 页面渲染都是在服务端完成的,即服务端运行过程中将所需的数据结合页面模板渲染为HTML,响应给客户端浏览器。所以浏览器呈现出来的是直接包含内容的页面。

在这里插入图片描述
在这里插入图片描述
在今天看来,这种渲染模式是不合理或者说不先进的。因为在当下这种网页越来越复杂的情况下,这种
模式存在很多明显的不足:

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

同构应用

  • 通过服务端渲染首屏直出,解决首屏渲染慢以及不利于 SEO 问题
  • 通过客户端渲染接管页面内容交互得到更好的用户体验
  • 这种方式通常称之为现代化的服务端渲染,也叫同构渲染
  • 这种方式构建的应用称之为服务端渲染应用或者是同构应用。

二、什么是渲染

我们这里所说的渲染指的是把(数据 + 模板)拼接到一起的这个事儿。
例如对于我们前端开发者来说最常见的一种场景就是:请求后端接口数据,然后将数据通过模板绑定语
法绑定到页面中,最终呈现给用户。这个过程就是我们这里所指的渲染。
渲染本质其实就是字符串的解析替换,实现方式有很多种;但是我们这里要关注的并不是如何渲染,而
是在哪里渲染的问题?

三、传统的服务端渲染

在这里插入图片描述
server.js

const express = require('express')
const fs = require('fs')
const template = require('art-template')

const app = express()

app.get('/', (req, res) => {
  // 1. 获取页面模板
  const templateStr = fs.readFileSync('./index.html', 'utf-8')

  // 2. 获取数据
  const data = JSON.parse(fs.readFileSync('./data.json', 'utf-8'))

  // 3. 渲染:数据 + 模板 = 最终结果
  const html = template.render(templateStr, data)

  // 4. 把渲染结果发送给客户端
  res.send(html)
})

app.get('/about', (req, res) => {
  res.end(fs.readFileSync('./about.html'))
})

app.listen(3000, () => console.log('running...'))

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>传统的服务端渲染</title>
</head>
<body>
  <h1>传统的服务端渲染</h1>
  <h2>{{ title }}</h2>
  <ul>
    {{ each posts }}
    <li>{{ $value.title }}</li>
    {{ /each }}
  </ul>
</body>
</html>

在今天看来,这种渲染模式是不合理或者说不先进的。因为在当下这种网页越来越复杂的情况下,这种模式存在很多明显的不足:

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

四、客户端渲染

传统的服务端渲染有很多问题,但是这些问题随着客户端 Ajax 技术的普及得到了有效的解决,Ajax 技术可以使得客户端动态获取数据变为可能,也就是说原本服务端渲染这件事儿也可以拿到客户端做了。

在这里插入图片描述
【后端】负责数据处理
【前端】负责页面渲染
【前端】更为独立,不再受限制于【后端】

但是存在不足:

  • 首屏渲染慢:因为 HTML 中没有内容,必须等到 JavaScript 加载并执行完成才能呈现页面内容。
  • SEO 问题:同样因为 HTML 中没有内容,所以对于目前的搜索引擎爬虫来说,页面中没有任何有用的信息,自然无法提取关键词,进行索引了。

五、现代化的服务端渲染

同构渲染:【服务端渲染】 + 【客户端渲染】

  • 首屏渲染慢
  • 不利于 SEO

解决办法就是将客户端渲染的工作放到服务端渲染,严格来讲应该叫现代化的服务端渲染,也叫
同构渲染,也就是【服务端渲染】 + 【客户端渲染】。

  • isomorphic web apps(同构应用):isomorphic/universal,基于 react、vue 框架,客户端渲染和服务器端渲染的结合
    (1)在服务器端执行一次,用于实现服务器端渲染(首屏直出)
    (2)在客户端再执行一次,用于接管页面交互
  • 核心解决 SEO 和首屏渲染慢的问题。
  • 拥有传统服务端渲染的优点,也有客户端渲染的优点

流程:
在这里插入图片描述

  • 客户端发起请求
  • 服务端渲染首屏内容 + 生成客户端 SPA 相关资源
  • 服务端将生成的首屏资源发送给客户端
  • 客户端直接展示服务端渲染好的首屏内容
  • 首屏中的 SPA 相关资源执行之后会激活客户端 Vue
  • 之后客户端所有的交互都由客户端 SPA 处理

优点:首屏渲染速度快、有利于 SEO
缺点

  • 开发成本高。
  • 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
  • 更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的server 更加大量占用 CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境(high traffic) 下使用,请准备相应的服务器负载,并明智地采用缓存策略

如何实现同构渲染?

使用Vue、React等框架的官方解决方案

  • 优点:有助于理解原理
  • 缺点:需要搭建环境,比较麻烦

使用第三方解决方案

  • React生态的Next.js
  • Vue生态的Nuxt.js

六、演示

npm i nuxt --dev

pages----->index.vue

<<template>
  <div id='app'>
      <h1>home</h1>
  </div>
</template>

<script>
export default {

}
</script>
<style scoped>

</style>


"scripts": {
    "dev":"nuxt"
 },

七、同构渲染应用的问题

  • 开发条件所限

    • 开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数 (lifecycle hook) 中使用。
    • 一些外部扩展库 (external library) 可能需要特殊处理,才能在服务器渲染应用程序中运行。
    • 不能在服务端渲染期间操作DOM
    • 某些代码操作需要区分运行环境

在这里插入图片描述

  • 涉及构建设置和部署的更多要求

  • 更多的服务端负载

    • 在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server更加大量占用 CPU 资源
    • 因此如果你预料在高流量环境 (high traffic)下使用,请准备相应的服务器负载
    • 需要更多地服务端渲染优化工作处理

服务端渲染使用建议

  • 首屏渲染速度是否真的重要?
  • 是否真的需要SEO?

八、Nuxt.js是什么

九、Nuxt.js使用方式

  • 初始化项目
  • 已有的Node.js服务端项目
    • 直接把Nuxt当作一个中间件集成到Node Web Server中
  • 现有的Vue.js项目
    • 非常熟悉Nuxt.js
    • 至少百分之10的代码改动

十、 路由-基本路由

Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。
假设 pages 的目录结构如下:

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

那么,Nuxt.js 自动生成的路由配置如下:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

十一、路由导航

  • a 标签 :会刷新整个页面,不要使用
  • 组件
  • 编程式导航
<template>
  <div id='app'>
    <h1>about页面</h1>

    <!-- a标签跳转 会刷新页面,不要使用 -->
    <h3>a标签跳转</h3>
    <a href="/">去首页</a>

    <!-- router-link跳转 -->
    <h3>router-link</h3>
    <router-link to="/">去首页</router-link>

    <!-- 编程式导航 -->
    <h3>编程式导航</h3>
    <button @click="onClick">点我跳转</button>
  </div>
</template>

<script>
export default {
  methods:{
      onClick(){
          console.log("123")
          this.$router.push("/")
      }
  }
}
</script>
<style scoped>

</style>

十二、动态路由

在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。

pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue

Nuxt.js 生成对应的路由配置表为:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    },
    {
      name: 'slug-comments',
      path: '/:slug/comments',
      component: 'pages/_slug/comments.vue'
    }
  ]
}

你会发现名称为 users-id 的路由路径带有 :id? 参数,?表示该路由是可选的。如果你想将它设置为必选的路由,需要在 users/_id 目录内创建一个 index.vue 文件。

十三、嵌套路由

创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。

Warning: 别忘了在父组件(.vue文件) 内增加 <nuxt-child/> 用于显示子视图内容。

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue

router: {
  routes: [
    {
      path: '/users',
      component: 'pages/users.vue',
      children: [
        {
          path: '',
          component: 'pages/users/index.vue',
          name: 'users'
        },
        {
          path: ':id',
          component: 'pages/users/_id.vue',
          name: 'users-id'
        }
      ]
    }
  ]
}

十四、动态路由

创建配置文件,nuxt.config.js

module.exports = {
  router: {
      //配置根路径
      base: '/abc',
      //routes:一个数组,路由配置表
      //resolve:解析路由组件路径
      extendRoutes(routes,resolve){
        routes.push({
          path: '/test',
          name: 'test',
          component: resolve(__dirname, 'pages/about.vue')
        })
      }
  }
}

/**
 * Nuxt.js 配置文件
 */

module.exports = {
  router: {
    linkActiveClass: 'active',
    // 自定义路由表规则
    extendRoutes (routes, resolve) {
    // 清除 Nuxt.js 基于 pages 目录默认生成的路由表规则
      // 这里不能 routes = [],赋值操作 引用就断掉了,形参传进来的是个数组引用
      //你是想清空 routes 数组的值,而不是想让 routes 重新指向空数组,
      //你是想让房间里的东西全部扔掉,还是想让房间钥匙恢复出厂设置
      routes.splice(0)
   //
      routes.push(...[
        {
          path: '/',
          component: resolve(__dirname, 'pages/layout/'),
          children: [
            {
              path: '', // 默认子路由
              name: 'home',
              component: resolve(__dirname, 'pages/home/')
            },
            {
              path: '/login',
              name: 'login',
              component: resolve(__dirname, 'pages/login/')
            },
            {
              path: '/register',
              name: 'register',
              component: resolve(__dirname, 'pages/login/')
            }
          ]
        }
      ])
    }
  },

  server: {
    host: '0.0.0.0',
    port: 3000
  },

  // 注册插件
  plugins: [
    '~/plugins/request.js',
    '~/plugins/dayjs.js'
  ]
}

在这里插入图片描述

十五、异步数据-asyncData方法

  • 基本用法

    • 它会将asyncData返回的数据融合组件data方法返回数据一并给组件
    • 调用时机:服务端渲染期间和客户端路由更新之前
  • 注意事项

    • 只能再页面组件中使用(子组件不能使用)
    • 没有this,因为他是再组件初始化之前被调用的
<template>
  <div id="app">
    <h2>{{ title }}</h2>
    <ul>
      <li
        v-for="item in posts"
        :key="item.id"
      >{{ item.title }}</li>
    </ul>
  </div>
</template>
  async asyncData () {
    const { data } = await axios({
      method: 'GET',
      url: 'http://localhost:3000/data.json'
    })
    // 这里返回的数据会和 data () {} 中的数据合并到一起给页面使用
    return data
  }

十六、异步数据-上下文对象

在这里插入图片描述

通过参数context获取id, 不能通过this.$route.params,因为获取不到this

  async asyncData (context) {
    console.log(context)
    const { data } = await axios({
      method: 'GET',
      url: 'http://localhost:3000/data.json'
    })
    const id = Number.parseInt(context.params.id)
    return {
      article: data.posts.find(item => item.id === id)
    }
  }
}

十七、Nuxtjs综合案例

请求数据在这里
在这里插入图片描述
nuxtServerInit 是一个特殊的 action 方法,这个 action 会在服务端渲染期间自动调用。作用:初始化容器数据,传递数据给客户端使用

store

export const actions = {
  // nuxtServerInit 是一个特殊的 action 方法
  // 这个 action 会在服务端渲染期间自动调用
  // 作用:初始化容器数据,传递数据给客户端使用
  nuxtServerInit ({ commit }, { req }) {
    let user = null

    // 如果请求头中有 Cookie
    if (req.headers.cookie) {
      // 使用 cookieparser 把 cookie 字符串转为 JavaScript 对象
      const parsed = cookieparser.parse(req.headers.cookie)
      try {
        user = JSON.parse(parsed.user)
      } catch (err) {
        // No valid cookie found
      }
    }

    // 提交 mutation 修改 state 状态
    commit('setUser', user)
  }
}

权限问题中间件
在这里插入图片描述
在受保护的组件里直接使用就可,

在这里插入图片描述

watchQuery API分页 监听路由参数变化, nuxtjs里面 路由参数query的变化默认是不会差法asyncData方法的

在这里插入图片描述
统一设置用户Token
在这里插入图片描述

用插件,在nuxt.config.js中注册
在这里插入图片描述
过滤器
在这里插入图片描述
https://www.cnblogs.com/yesyes/p/7977161.html

文章详情-把Markdown转为HTML

http://markdown-it.docschina.org/api/ParserInline.html#parserinline

文章详情-页面设置meta优化SEO

在这里插入图片描述

meta标签里面有个content属性
在这里插入图片描述
打包
在这里插入图片描述
npm run build 生成.nuxt文件 npm run start可以运行打包后的文件

十八、发布部署

最简单的部署方式

  • 配置Host + Post
  • 压缩发布包
  • 把发布包传到服务器
  • 解压
  • 安装依赖
  • 启动服务
    1、配置一下这里
    在这里插入图片描述
    不能用localhost,localhost只能本机访问

2、把这几个文件提取出来压缩一下(带上pages和components 不然报错) 或者把所有项目部署上去
在这里插入图片描述
3、连接服务端
ssh root@39.105.28.5
4、创建服务端目录
mkdir realword-nuxtjs
5、进入目录
cd realword-nuxtjs/
6、pwd 打印一下路径
在这里插入图片描述
7、exit 退出服务端
在这里插入图片描述
8、把压缩包 传到远程服务器目录下
在这里插入图片描述
9、再连接服务器
ssh root@39.105.28.5
10、进入目录
cd realword-nuxtjs/
11、ls看到列表
在这里插入图片描述
12、解压压缩包
unzip realword-nuxtjs.zip
13、ls -a看到隐藏目录
在这里插入图片描述
14、 第三方包安装下
npm i

15、启动
npm run start

十九、发布部署-使用PM2启动Node服务

在这里插入图片描述
当我们退出服务的时候,程序也就停止了,这是需要一个程序去维护

pm2就是把程序运行在后台,使其保持运行状态

1、在服务端项目目录下安装
npm install --global pm2
在这里插入图片描述
2、启动
pm2 start npm --start(pm2 start pm2.config.json) (要等待两分钟 资源才能加载出出来)
在这里插入图片描述
3、关闭服务npm stop + id
npm stop 6
在这里插入图片描述
4、常用命令
在这里插入图片描述
pm2 kill杀死所有进程
调试案例:
https://blog.csdn.net/qq_39905409/article/details/91357691?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-2-91357691.pc_agg_new_rank&utm_term=pm2%E5%90%AF%E5%8A%A8%E7%9A%84node%E9%A1%B9%E7%9B%AE%E8%AE%BF%E9%97%AE%E4%B8%8D%E4%BA%86&spm=1000.2123.3001.4430

二十、传统的部署方式

  • 更新

    • 本地构建
    • 发布
  • 更新

    • 本地构建
    • 发布

现代化的部署方式 (CI / CD)
在这里插入图片描述

二十一、发布部署-准备自动部署内容

使用GitHub Actions实现自动化部署

CI / CD服务

  • Jenkins
  • GitLab CI
  • GitHub Actions
  • Travis CI
  • Circle CI

环境准备

  • Linux服务器
  • 把代码提交到GitHub远程服务器

配置GitHub Access Token

在远程仓库里面点击setting
在这里插入图片描述
点进去 点击secrets
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
配置GitHub Actions执行脚本

name: Publish And Deploy Demo
on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:

    # 下载源码
    - name: Checkout
      uses: actions/checkout@master

    # 打包构建
    - name: Build
      uses: actions/setup-node@master
    - run: npm install
    - run: npm run build
    - run: tar -zcvf release.tgz .nuxt static nuxt.config.js package.json package-lock.json pm2.config.json

    # 发布 Release
    - name: Create Release
      id: create_release
      uses: actions/create-release@master
      env:
        GITHUB_TOKEN: ${{ secrets.TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: Release ${{ github.ref }}
        draft: false
        prerelease: false

    # 上传构建结果到 Release
    - name: Upload Release Asset
      id: upload-release-asset
      uses: actions/upload-release-asset@master
      env:
        GITHUB_TOKEN: ${{ secrets.TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: ./release.tgz
        asset_name: release.tgz
        asset_content_type: application/x-tgz

    # 部署到服务器
    - name: Deploy
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        password: ${{ secrets.PASSWORD }}
        port: ${{ secrets.PORT }}
        script: |
          cd /root/realworld-nuxtjs
          wget https://github.com/lipengzhou/realworld-nuxtjs/releases/latest/download/release.tgz -O release.tgz
          tar zxvf release.tgz
          npm install --production
          pm2 reload pm2.config.json

  • 修改配置
    这里要注意
    在这里插入图片描述
    然后配置这些
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

  • 配置PM2配置文件
    在根目录创建pm2.config.json
{
  "apps": [
    {
      "name": "RealWorld",
      "script": "npm",
      "args": "start"
    }
  ]
}

  • 提交更新
  • 查看自动部署
  • 访问网站
  • 提交更新…

二十二、发布部署-自动部署完成

首先确保仓库有了最基本的代码提交
在这里插入图片描述
git add .
git tag v0.1.0
git push origin v0.1.0
然后查看tags
在这里插入图片描述
然后查看Action
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果我们代码有更新
继续
git add.
git commit -m"长威"
git push
git tag v0.1.1
git push origin v0.1.1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值