Nuxt项目踩坑总结

6,727 阅读8分钟

年中总结,用Nuxt.js写了几个月的项目终于修修改改要上线了,不容易啊。
文章会先从为什么使用Nuxt.js进行切入,再讨论在使用过程中遇到的问题跟解决的方法。

正文

1、为什么要使Nuxt.js?

在网站需要SEO的时候,前端的开发流程大致分为:会套后台模板跟不会套后台模板分成两种。

不会套模板就很麻烦,每次写完页面交给后台去套,套完前端还要去检查样式是不是原来的样子,有没有变,再来接接口,所以经常就是在催后台

现在提倡前后端分离,我们应该怎样即要实现前后端分离又要实现SEO优化呢?

感谢大佬们创造了 vue-server-renderer  插件,具体的使用可查看官网给出的教程。

一句话概括插件的原理:把需要展示出来的数据请求放在了node层去处理,然后读取相应页面的配置文件把数据输出到页面上。


既然有了这个插件我为什么要用Nuxt.js          

因为在用插件搭建服务端渲染的时候,无法解决文件热更新导致整个运行程序崩溃问题

从SPA形式切到Nuxt.js可以无缝切换,没有什么特别大的难点,前提是你通读了官方文档。

PS:如果百度找不到答案,可以去Nuxt.js 的 github 看看 issue ,这里面基本可以找你想要的答案,这个办法真的非常管用。

基础应用与配置    

创建项目


       

项目的配置,我选择的是:
       

  • 服务端:Koa    // 如果你想在node层做一些操作的话请选择node框架更灵活

  • UI框架:None

  • 测试框架:Jest

  • Nuxt模式:Universal   //这个是服务端渲染的,  SPA模式没有服务端渲染

  • 使用集成的 Axios

  • 使用 EsLint


       

环境变量

实际项目中有本地,测试,灰度,生产等环境,使用 @nuxtjs/dotenv 可以帮助我们方便的管理我们的环境变量    


安装
npm i @nuxtjs/dotenv -s

新建一个文件名为 .env的文件,也可以叫其他名字(.env是默认读取的文件名,可查看官方文档获取更多姿势)

需要去 nuxt.config.js 中配置modules模块


   

nuxt.config.js    

module.exports = {

    modules: [

        ['@nuxtjs/dotenv'] //这里没有做其他参数传入,会默认读取目录下的.env文件,如果是叫prod.env,应该写成['@nuxtjs/dotenv', { filename: '.env.prod' }]

  ],

}


以baseUrl 为例:

DEV_BASE_URL = 'http://xxx.cn'

TEST_BASE_URL = 'https://xxx.cn'

PROD_BASE_URL = 'https://xxx.cn'

package.json  配置启动脚本命令 加入变量BASE    

{
    "scripts":{
        "dev": "cross-env NODE_ENV=development BASE=dev nodemon server/index.js --watch server",
        "build-test": "cross-env NODE_ENV=production BASE=test nuxt build", 
        "start-test": "cross-env NODE_ENV=production BASE=test node server/index.js",
        "build": "cross-env NODE_ENV=production BASE=prod nuxt build", 
        "start": "cross-env NODE_ENV=production BASE=prod node server/index.js",
        "generate": "nuxt generate"
    }
}

nuxt.confug.js
require('dotenv').config()
let baseUrl = ''
switch (process.env.BASE) {
  case 'dev':
    baseUrl = process.env.DEV_BASE_URL
    break;
  case 'test':
    baseUrl = process.env.TEST_BASE_URL
    break;
  case 'prod':
    baseUrl = process.env.PROD_BASE_URL
    break;
  default:
    baseUrl = process.env.PROD_BASE_URL
    break;
}

module.exports = {
  env: {
     baseUrl: baseUrl,  //  全局注入环境变量baseUrl,访问 process.env.baseUrl
  }
}


异步请求

官方推荐使用 @nuxtjs/axios ,虽然少了 axios 的一些api,但是它都强烈推荐了,这坑还是不要踩了。
   

   

But,我找了半天,它没有跟说怎么在项目里进行封装啊,难道把所有的接口都写在plugins里进行全局注入?emmm,所以我又用了 axios。如果你们知道请告诉我。    


   

asyncData方法

服务端渲染执行的就是这个方法,需要SEO的页面数据的初始化请求放在这个方法里。

需要注意的是,asyncData方法只能在页面组件里执行,在布局组件跟子组件里加是没有用的,子组件的数据也想要SEO就只能通过props传递过去。

这么写:

export default {
  asyncData ({ params }) {
    return axios.get(`https://my-api/posts/${params.id}`)
      .then((res) => {
        return { title: res.data.title }
      })
  }
}

咦,怎么页面的数据不出来? 不要方,加上async 和 await

export default {  
    async asyncData ({ params }) {    
        const { data } = await axios.get(`https://my-api/posts/${params.id}`)    
        return { title: data.title }  
    }
}

再看看是不是可以了呢。


这里需要注意,渲染页面是在asyncData之后才进行的,如果在这写多个接口请求没有做优化,那么页面的白屏时间将会很长~

为了提高渲染效率,那么我们必须要缩短这个请求的时间,这里需要后台同学配合将多个接口聚合成一个接口,我们也可以用 axios.all(promise.all同理) 并发请求。 千万要记得写 catch 捕获异常。


这里还有一段话需要注意:
asyncData is called every time before loading the page component and is only available for such. It will be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes. This method receives the context object as the first argument, you can use it to fetch some data and return the component data.

Plugins

官网是这么说的:

Nuxt.js允许您在运行Vue.js应用程序之前执行js插件。这在您需要使用自己的库或第三方模块时特别有用。


意思就是在这写完了某个函数或属性值,注入到Vue实例(客户端),context(服务器端)甚至 store(Vuex)。就可以在这些地方使用你写的函数或属性值。


例如加个通知信息插件(自定义全局组件同理)

安装完插件后,在 plugins 目录中增加文件 vue-notifications.js

import Vue from 'vue'
import VueNotifications from 'vue-notifications'Vue.use(VueNotifications)

在 nuxt.config.js  中需要配置plugins    

export default {
  plugins: [
     { src: '~/plugins/vue-notifications', ssr: false }  
  ]
}

不支持ssr的系统插件只在浏览器使用,可以在插件后面加上 ssr:false 设置

个人观点,不建议在这引入大量的插件,不然等会打包出来的体积挺大
我在项目中主要用来注册全局filters跟全局组件,其他页面的插件尽量做到哪里用哪里引。


在组件中使用引入插件报错:Window 或 Document 对象未定义?

在组件中使用日期的插件时看到这个问题,猜测:是不是组件脚本在服务端(node层)被执行了?去掉就正常
   

官方文档-》常见问题中可以找到了解决的方法。

改前:
import * as lang from "vuejs-datepicker/src/locale/index.js";
import Datepicker from "vuejs-datepicker";
改后:
let lang = null, Datepicker = null; 

if (process.client) { 
 lang = require("vuejs-datepicker/src/locale/index.js")["zh"];//只引入中文的语言包
 Datepicker = require("vuejs-datepicker/dist/vuejs-datepicker.js")
}

css预处理器

我用的是less语法,为了方便,不用每次都引入,所以全局注入变量 和 mixin 就不用每次都去导入了,可以使用 @nuxtjs/style-resources 来实现。
   

安装    

npm i @nuxtjs/style-resources --save--dev

配置    

nuxt.config.js     

module.exports = {
    modules: [
        "@nuxtjs/style-resources"
    ],
    styleResources: {
        less: [
            "./assets/scss/variable.less" // 务必使用相对路径,这里不能用@跟~
        ]
    },
}

权限验证

官网在路由中有一个中间件的概念,给出的定义是这样的:    

中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前

怎么这定义看起来很像导航守卫

没错,他们的作用是相似的,不同的是这个中间件它 既在服务端调用也在客户端调用。也可以在某个页面单独调用中间件

1.nuxt.config.js全局配置:
export default {
...
  router: {
    middleware: 'stats'
  }
}
2.pages/index.vue在页面中单独配置:
export default {
  middleware: ['auth', 'stats']
}

你可以在项目中做以下尝试来理解这句话。


在项目的 middleware/auth.js 中 打印你请求头里的user-agent

export default function (context) {
 context.userAgent = process.server ? context.req.headers : navigator.userAgent;
 console.log(context.userAgent)
}

会看到:

- 首次进入页面 / 刷新页面 / 跳转到新标签页 时, userAgent 会在命令台打印

-当前窗口跳转路由 时, userAgent 会在浏览器的控制台打印。

既然如此,我们可以在这做权限验证、路由拦截这些事

Token存放

PC端的网站一般都是多页面,不是spa模式,因此不考虑使用Vuex进行存取了。

要做到多个页面同步数据, 可以考虑存放在 localstorage 中,浏览器也有提供相关的api进行监听,具体可参照MDN文档

window.addEventListener("storage", function(e) { 
 
});

需要注意的地方:

  • 需要服务器,静态页面测试无效果

  • 必须是同源(想跨域得使用postMessage,但是会有安全问题)

  • 页面只能收到别的页面发送的通知


要想省事,就需要的时候再去请求接口拿token,什么烦恼都没有~

   

打包优化


打包完vendors.app.js超过4M,太太大了吧。 我来看看是哪个小老弟在搞事情, 把analyze开起来。

nuxt.config.js

export defualt{
    build: {
        analyze: true, 	// 开启打包分析
    }   
}

npm run build 一下,打完包会自动在浏览器打开页面

问题很明显了,开始优化: aliyun的上传SKD占大头,动态加载,其他插件哪里用到哪里加。

统统改一遍后:

再把gzip一开, 妥了妥了

如果项目中用到UI框架的, 建议 按需加载 或者走 CDN

首屏效果如何呢


来看看 performance 的结果

渲染速度跟原来后台渲染的基本没有差距,没有长时间的白屏等待.


未完待续


  • 异常捕获,错误上报,监控这一方面

  • 如何在项目中加上typescript(这也是一个坑,差点就出不来了)

  • 做一点简单的自动化测试

  • ...


   

总结


Nuxt.js的英文文档确实会比中文的多一些信息,中文的有些地方翻译奇怪,语意不通,还是看他的英文文档好一点。

框架属于 “约定大于配置”,如果先用vue-server-renderer 插件搭一遍,再用这个框架会更透彻一些。

从学习到小范围测试,再到大型项目上线,快一年了,有些坑踩着踩着就忘了,哪天想起来会回来继续补充。

欢迎大家留言交流~