nuxt踩坑合集
原因分析
- 内部原因:
- 调研时间不足。
- 撸起袖子就写代码是一个错误的方式,现在回头看m站会发现很多不对的实现方式,个别模块的优化不亚于重写。
- 技术了解不足,科学的使用方式后知后觉。
- 构建环境问题(不同环境相同代码,结果不同;打静态页面不可取等)。
- 插件使用问题(sentry竟会阻塞代码,有待研究)。
- 文档,API了解不足。
- 思维方式陈旧。
- 虽然是基于vue的框架但是使用方式与vue-cli构建的应用差别甚远,用vue的那套思想会让我们走入歧途。(比如axios请求,客户端与服务端不同)
- 调研时间不足。
- 外部原因:
- 新兴技术,自带坑;尤其是升级大版本带来的问题。
- 文档不全、文档更新不及时。
- 建议使用英文版的官网,版本高于中文版。
- 官方的文档可能是旧的。
- 到目前为止nuxt的谷歌分析统计都是错误的实例。nuxt2.0版本之后已不适用,改用了analytics-module
- 插件兼容导致的问题。
- 三方插件不支持ssr方式
- babel-pollify编译(babel只是将一些es6,es7-8的语法转换成符合目标的js代码,但是如果我们使用一些特性或方法,比如Generator, Set, 或者一些方法。babel并不能转换为低版本浏览器识别的代码。这时就需要babel-polifill)
一、 nuxt是什么?为什么要使用nuxt?
2016 年 10 月 25 日,zeit.co 背后的团队对外发布了 Next.js,一个 React 的服务端渲染应用框架。几小时后,与 Next.js 异曲同工,一个基于 Vue.js 的服务端渲染应用框架应运而生,我们称之为:Nuxt.js。
Nuxt.js 是一个基于 Vue.js 的通用应用框架。
通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。 其核心主要是通过vue-server-renderer模块来实现服务端渲染。 vue-server-renderer是Vue服务端Node.js渲染的一个模块,用来生成HTML内容。
我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。
Nuxt.js 预设了利用Vue.js开发服务端渲染的应用所需要的各种配置。 除此之外,我们还提供了一种命令叫:nuxt generate,为基于 Vue.js 的应用提供生成对应的静态站点的功能。 我们相信这个命令所提供的功能,是向开发集成各种微服务(microservices)的 Web 应用迈开的新一步。 作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。
SSR意为 server-side rendering(服务端渲染),目的是为了解决单页面应用的SEO的问题。
-
客户端渲染和服务器端渲染的最重要的区别是html文件的返回是在服务端还是客户端。若是在服务器端完成的,就是服务器端渲染;反之前端完成了html的拼接,则就是客户端渲染(一般为首屏渲染: vue-meta-info + prerender-spa-plugin)。
-
SEO对比:collinss.cn 和 en.tourscool.com
二、为什么选择nuxt
1. 服务器端渲染的优缺点?
优点:
- 更好的SEO 首屏加载快 由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
- 首屏加载快 快速地看到完整渲染的页面,从而提高用户体验。
- 后端生成静态化文件 即解析模板的工作完全交由Nuxt来做,客户端只要解析标准的html。
缺点:
- 开发条件受限,在服务端渲染中,created和beforeCreate之外的生命周期钩子不可用;其他生命周期均为客户端。
- 占用服务端资源,所以原则是不影响SEO数据展示的内容或者通过事件触发获取的数据还是通过客户端获取,以减轻服务端压力。
- 学习成本相对较高 除了对webpack、Vue要熟悉,还需要掌握node、Express或者koa等相关技术。相对于客户端渲染,项目构建、部署过程更加复杂。
2. 是否应该使用服务端渲染
- 首屏加载慢
- SEO优化
3. 原理图
4. 指令背后的故事
三、使用过程中遇到的坑
1. window is undefined
第一个“坑”就是ssr本身导致的。不管使用element-ui或者mint-ui或者其他的UI框架。都是基于window环境而非node环境。
# vue的情况下,我们是这样使用的
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper'
# 插件注入
Vue.use(VueAwesomeSwiper)
修改为支持ssr,否则就会出现window is undefined
# 做成服务端渲染需要注意引入时机
import Vue from 'vue'
# 判断环境——browser代表客户端
if (process.browser) {
const VueAwesomeSwiper = require('vue-awesome-swiper/dist/ssr')
Vue.use(VueAwesomeSwiper)
}
2.【移动端】因为调用app端的方法都是使用window下的android和webkit.messageHandlers方法。但是ssr下如果直接引入appBridge.js下的方法会报错,因为服务端没有window
这个问题还是因为ssr本身的“坑”,也属于是框架了解不足。在服务端运行时无window下的对象。我的处理方式是在mounted生命周期下才引入appBridge.js。官方还有处理方式是判断当前运行环境(通过process.client)。【还没试过的方式,通过nuxt.config.js配置:plugins + ssr:false】
vue里面这样用的(非ssr):
import {hideNavigationBar} from '@/assets/js/appBridge'
export default {
// code...
}
ssr:
# 在需要调用app端的页面
export default {
data() {
return {
isApp: this.$route.query.platform || null,
}
},
mounted() {
if (this.isApp) {
// 引入appBridge,后面可以使用
this.appBridge = require('@/assets/js/appBridge').default
this.appBridge.hideNavigationBar()
} else {
console.log('web操作')
}
}
}
实际上在服务的已加是能获取到设备型号的,然后直接存store。应该是更好的方式。至于引用appBridge的方式暂时没有想到好方法。 可以使用process.client判断是客户端
3. [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside, or missing . Bailing hydration and performing full client-side render.
服务端与客户端渲染的DOM结构不一样,HTML结构嵌套不正确,但大多数情况下并不是嵌套结构的问题,而是因为==服务端与客户端渲染的DOM结构不一样==
- 错误嵌套代码示例:
# 错误原因<p><div></div></p> 或 <span><p></p></span>
<p class="text">
<van-checkbox class="tour-checkbox"
v-model="checked">
<span>我已经阅读并同意《服务协议》</span>
</van-checkbox>
</p>
修正:
<div class="text">
<van-checkbox class="tour-checkbox"
v-model="checked">
<span>我已经阅读并同意《服务协议》</span>
</van-checkbox>
</div>
- 服务端与客户端渲染的DOM结构不一致
# nuxt.config.js配置
plugins: [
{
src: '~/plugins/mint-ui',
ssr: true // 划重点!!!设为true后,ssr会进行初始化时前后dom比较,保证初始化前后一致
}, {
src: "~/plugins/vue-swiper",
ssr: false // 禁用服务端渲染
},
]
这边以 vue-awesome-swiper 为例,官网也给出了明确的ssr使用方式,请仔细阅读,一定要用ssr的方式!
例子中的类名.swiper-wrapper类名千万不要修改。
4. 【移动端】postcss-px2rem会导致其他本身已支持自适应的UI库(vant UI)样式缩小。
解决方式: postcss-px2rem-exclude代替postcss-px2rem,配置如下
# nuxt.config.js
build:{
postcss: [
require('postcss-px2rem-exclude')({
remUnit: 75, // 转换基本单位
exclude: /vant/i
}),
require('autoprefixer')({
browsers: ['last 3 versions']
})
],
}
5. this.$route.params一直为空,但query是正常。郁闷了我一个小时。
这个坑我不想说什么了。可能是用vue就习惯了用path跳转,但在nuxt中可能要换成使用name了,因为用params传参这里就必须带上name,但为什么必须带name我不清楚【或许是nuxt中的pages就是遍历出来的路由所以用了pages下的文件名作为name,而自定义的name是失效的】。
有兴趣可以试试:自定义name不生效,而是使用创建好的pages下的页面名,比如demo/test.vue(name为’demo-test’)
错误代码:
this.$router.push({
path: '/demo/test', // demo-test
params: {
type: this.type
}
})
正确代码:
this.$router.push({
name: 'demo-test', // 划重点!!!,params须用name!(pages/demo/test.vue)
path: '/demo/test',
params: {
type: this.type
}
})
6. vuex store (nuxt.js 提供了2种store modes)
Modules: every .js file inside the store directory is transformed as a namespaced module (index being the root module).(不需要new Vuex.Store)
Classic (deprecated): store/index.js returns a method to create a store instance. 这个2.4x版本已经提示要作废了。nuxt3将不存在此用法
我的store目录结构(使用Modules mode):
├─state.js
├─actions.js
├─getters.js
├─mutations.js
├─login(login模块)
├─index.js
// state.js 必须导出返回对象的方法,创建独立实例
export default () => ({
profile: {},
})
// getters.js,actions.js,mutations.js都是直接导出对象
export default {
// 方法
}
// login/index.js
export const state = () => ({
loginData: {
time: new Date()
}
})
export const actions = {}
export const mutations = {}
7. 工厂模式api
issue中有人提出了https://github.com/nuxt-community/axios-module/pull/102
# 使用axios module
let apiConfig = require('~/apiConf.env')
export default function ({
$axios,
redirect
}) {
// 设置基础配置
$axios.defaults.timeout = 10000
$axios.defaults.withCredentials = true
$axios.defaults.headers = {
'Content-Type': 'application/json; charset=utf-8', // json格式通信
'platform': 'pc',
}
if (process.env.NODE_ENV === 'production') {
$axios.defaults.baseURL = apiConfig.base
}
// post请求头设置
// $axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
// 请求回调
$axios.onRequest(config => {
console.log('Making request to ' + config.url)
return config
})
// 响应回调
$axios.onResponse(res => {
console.log('Making response to ' + res.url)
return res.data
})
// 错误回调
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
console.log('400')
}
})
}
8. yarn generate 打静态包问题
如果你想使用nuxt generate打静态文件的包,那么狠遗憾,动态路由无法静态打包;并且你将失去服务端渲染的一切优势。asyncData等服务端能用的方式,都不能再使用。那你为什么还要用静态打包.但是如果你的项目只是静态页面的话,做静态化部署是完全OK 的。但如果牵扯到请求,还是服务器端的部署吧
官网所给的方式很气人: https://zh.nuxtjs.org/api/configuration-generate
const axios = require('axios')
module.exports = {
generate: {
routes: function () {
return axios.get('https://my-api/users')
.then((res) => {
return res.data.map((user) => {
return '/users/' + user.id
})
})
}
}
}
// 实际上也是通过axios.get('https://my-api/users')请求,返回所有id的数组,类似:[{id: 1}, {id: 2}...];
// 数据量大的话自己想象。不喜欢query传参,但目前我个人觉得打包成静态的文件的话,还是使用query是最好的方式了。
动态路由静态打包: 在nuxt.js的issues找到了这个:https://github.com/nuxt/nuxt.js/issues/73 可以参考
构建
- core-js问题
https://github.com/nuxt/nuxt.js/pull/5317
https://github.com/nuxt/nuxt.js/pull/5411
解决方式:https://github.com/nuxt/nuxt.js/pull/5352
- less3.0bug,iview主题色修改遇到
// https://github.com/ant-design/ant-motion/issues/44
extend(config, ctx) {
config.module.rules.push({
test: /\.less$/,
loader: 'less-loader',
options: {
javascriptEnabled: true,
}
})
}
- extractCSS与parallel不可并行;IE或者Edge下报错原因
// analyze: true,
// extractCSS与parallel不可并行:https://github.com/nuxt/nuxt.js/pull/5004
extractCSS: (process.env.NODE_ENV === 'production'), // 拆分css
maxChunkSize: 30000,
// parallel: true, // 多进程
// IE或者Edge下报错原因:(https://github.com/Rich-Harris/devalue/issues/16)
// 处理
// https://github.com/nuxt/nuxt.js/issues/4432
// https://github.com/nuxt/nuxt.js/issues/4643
// https://github.com/nuxt/nuxt.js/pull/4600