学习笔记(二十一)NuxtJS综合案例

中间跨了个春节,过完年手贱将做了一个礼拜的案例代码给删了,加上最近工作有点忙,学习也变的懈怠了,很久没有更新了
最近终于把之前的案例部分又重新做了遍,使用Github Actions自动化部署到自己的服务器,以及自动化部署到Vercel都给安排上
争取把学习进度赶回来吧

NuxtJS综合案例

案例介绍

案例相关资源

使用NuxtJS来实现案例

问题与解决方案

在使用NuxtJS实现整个RealWorld案例的过程中,会碰到各种各样的问题,这里记录一下碰到的各种问题与相应的解决方案

静态资源加载

这个案例中使用到的一些CSS、字体文件等静态资源来源于国外的网站,在国内环境不使用"科学上网"方式来访问加载时会非常的缓慢

解决方案:

  • 尝试使用jsDelivr网站,这个网站为npm、github、wordpress、deno等多个平台的开源资源提供免费的CDN地址

  • 在该网站上搜索相应的静态资源库名称,例如我们案例中使用到的ionicons.min.css,找到相对应的版本,并获取cdn链接

  • https://www.jsdelivr.com/

    image-20210203210747636
  • 对于非开源独立部署的静态资源,无法在jsDelivr上找到相应的CDN地址,可以将相应的资源手动下载下载,直接放到项目目录中进行本地访问

登录状态存储与持久化

我们在使用Vue开发前端项目时,通常使用Vuex来存储状态,并将单一状态树对象存储到localStorage中来实现状态的持久化

NuxtJS默认集成了Vuex,但NuxtJS应用在进行服务端渲染时,无法使用浏览器环境下的localStorage

解决方案:

NuxtJS官方提供的一种解决示例是将状态存储到Cookie中,以此实现服务端与客户端之间的状态共享与持久化存储

浏览器环境的Cookie有大小限制,不同浏览器之间存在差异,通常为4K

// store/index.js
// NuxtJS 中 Vuex Store 示例
const cookieparser = process.server ? require('cookieparser') : undefined
 
// 服务端渲染运行期间都是同一个实例
// 为了防止数据冲突,必须把 state 定义成一个函数,返回数据对象
export const state = () => {
  return {
    user: null
  }
}
 
export const mutations = {
  setUser (state, data) {
    state.user = data
  }
}
 
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)
        commit('setUser', user)
      } catch (error) {
 
      }
    }
  }
}
// 使用 Cookie 存储状态数据示例
const Cookie = process.client ? require('js-cookie') : undefined
 
...
...
 
this.$store.commit('setUser', data.user)
// 使用 Cookie 存储状态数据实现持久化
Cookie.set('user', data.user)

页面访问鉴权

使用Vue开发前端应用时,对于页面访问的鉴权控制,通常可以使用路由拦截器(全局前置导航守卫beforeEach)来实现

对于NuxtJS开发的应用,当进行服务端渲染时,应当在进入页面处理之前就需要进行鉴权拦截,官方提供了通过路由中间件middleware的方式来实现页面访问鉴权的处理,可以同时支持客户端和服务端渲染的路由拦截

NuxtJS路由中间件

  • 中间件是一个自定义的函数,接收context作为第一个参数,运行于一组页面渲染之前
  • NuxtJS约定中间件定义的JS文件需放置在middleware目录下,JS文件的名称将作为中间件的名称
  • 在需要使用中间件的页面组件中,通过middleware选项指定使用的中间件名称,可以使用数组同时指定多个需要执行的中间件
// middleware/authenticated.js
// 中间件定义示例
/**
* 验证是否登录的中间件
*/
 
export default function ({ store, redirect }) {
  if (!store.state.user) {
    return redirect('/login')
  }
}

监听Query变化

实现首页数据分页处理的过程中,服务端渲染在asyncData()中加载页面数据,通过URL Query中增加page参数来获取当前访问的分页页码,进而处理接口获取相应的分页数据

分页的触发可以直接使用a标签,但这样会触发页面刷新,重新请求服务端端渲染

使用nuxt-link组件代替a标签,可以触发Query的变化,但是并不会触发页面接口数据的重新加载

NuxtJS官方提供了watchQuery属性,将监听配置所指定的Query参数字符串,当监听的Query参数字符串发生变化,将重新调用所有组件方法(asyncData/fetch/validate/layout等)

出于性能考虑,该属性默认禁用

使用示例

export default {
    // 支持指定多个Query参数
     // 如果要监听所有参数则直接设置为 true
    watchQuery: ['page']
}

axios拦截器添加Token

对于axios请求,通常会单独定义JS文件来创建实例,并封装处理请求拦截器与响应拦截器,请求拦截器中往往需要向header中添加用户鉴权的toekn,而用户token存储在store中,无法在拦截器中直接访问,NuxtJS提供了plugins,自定义plugin是一个函数,接收context上下文对象作为参数

plugns需要在nuxt.config.js配置文件中通过plugins选项进行注册

// plugins/request.js
/**
* 基于 axios 封装的请求模块
*/
 
import Axios from "axios"
 
export const axios = Axios.create({
  baseURL: 'https://conduit.productionready.io/',
})
 
// 通过插件机制获取上下文对象(query、params、req、res、app、store...)
export default ({ store }) => {
  // 请求拦截器
  axios.interceptors.request.use(function (config) {
    const { user } = store.state
 
    if (user && user.token) {
      config.headers.Authorization = `Token ${user.token}`
    }
    return config
  }, function (error) {
    return Promise.reject(error)
  })
 
  // 响应拦截器
 
}
// nuxt.config.js
module.exports = {
  ...
  plugins: [
    '~/plugins/request'
  ]
}

Markdown支持

文章发布的内容支持markdown语法编辑提交,提交后的内容需要转换成HTML格式进行展示

这里借助第三方库markdown-it,将内容转换成HTML字符串,并使用v-html指令进行展示

import { getArticle } from '@/api/article'
import MarkdownIt from 'markdown-it'
import ArticleMeta from '@/components/ArticleMeta'
export default {
  name: 'Article',
  components: {
    ArticleMeta
  },
  props: {},
  async asyncData ({ params }) {
    const { data } = await getArticle(params.slug)
    const { article } = data
    const md = new MarkdownIt()
    article.html = md.render(article.body)
 
    return {
      article
    }
  },
  data () {
    return {
    }
  },
  computed: {},
  created () { },
  mounted () { },
  methods: {}
}

设置页面Meta优化SEO

发布的不同的文章内容与标签需要进行SEO的优化,需要动态设置页面的Meta

NuxtJS可以通过nuxt.config.js来配置固定的Meta内容,使用head: {meta: {}}选项

如果想要针对不同的页面个性化定制Meta,NuxtJS在页面组件中提供了额外的head()方法,该方法返回一个配置对象,可以包含titlemeta等属性

export default {
  name: 'Article',
  head () {
    return {
      title: `${this.article.title} - RealWorld`,
      meta: [
        { hid: 'description', name: 'description', content: this.article.description }
      ]
    }
  },
  ...
}

发布部署

打包

NuxtJS提供的命令

  • nuxt:开发模式编译带热更新

  • nuxt build:进行生产模式打包,打包生成.nuxt目录,以及.nuxt/dist目录(开发模式下不生成dist目录)

  • nuxt start:启动生产模式打包的内容

  • nuxt generate:生成静态HTML资源

这里我们使用nuxt build进行生产模式打包,将打包后的.nuxt目录,以及static目录、nuxt.config.js配置文件、package.jsonpackage-lock.json一起上传到服务器

部署

首次部署之前,需要配置nuxt.config.js中的server选项,指定hostport

module.exports = {
  ...
  server: {
    host: '0.0.0.0',
    port: 8080 // 默认3000
  }
}

NuxtJS官方提供了各种平台、容器的部署方式指南,包括常用的PM2、NGINX、Github、Google App Engine、Vercel等近20种,相应方式的部署可以参考官方的指南及注意事项进行配置

自动化部署

image-20210206124247970

项目日常开发与运维过程中,项目代码更新、打包、上传、部署这一系列操作需要反复的重复执行,将这些重复的工作通过自动化的方式去执行,可以减少重复工作,也符合DevOps的思想

常见的CI/CD服务有很多

  • Jenkins
  • Gitlab CI
  • Github Actions
  • Gitee Go
  • Travis CI
  • Circle CI
  • ...

Github Actions

以Github Actions为例来实现自动化部署NuxtJS项目

准备工作

  • 在github上创建项目仓库并将项目代码上传

  • 准备一台用于部署的服务器(一般使用linux服务器)

配置Github Access Token

配置Github Actions执行脚本

  • 在项目根目录创建.github/workflows目录
  • 下载main.ymlworkflows目录中
  • 修改配置
# main.yml 示例
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
 
      # 发布 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/rpyoyo/realworld-nuxtjs/releases/latest/download/release.tgz -O release.tgz
            tar zxvf release.tgz
            npm install --production
            pm2 reload pm2.config.json
 

发布更新内容到Github并触发自动化部署

  • 使用git tag v1.0.0命令创建tag标签
  • git push origin v1.0.0提交tag标签触发自动化部署

在github项目的actions中可以查看到正在执行中的部署过程

image-20210324101403124

自动化部署完成后,访问服务器的相应的地址与端口,确认是否可以正常访问页面

image-20210324101556859

写在最后

NuxtJS官方提供了多达几十种部署方案相应的指南,参照这些指南可以方便地将我们的NuxtJS项目以我们自己想要的方式或者平台进行部署,例如部署到Vercel

我所完成的这整个案例

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容