# 介绍

Nuxt.js 是一个基于 Vue.js 的通用应用框架。支持 spa 模式、ssr 模式、静态化模式
nuxt=vue+ vue-router + Vuex + Vue.js Server-Side + Vue-Meta + webpack

# 特性

  • 基于 Vue.js
  • 自动代码分层
  • 服务端渲染
  • 强大的路由功能,支持异步数据
  • 静态文件服务
  • ES2015+ 语法支持
  • 打包和压缩 JS 和 CSS
  • HTML 头部标签管理
  • 本地开发支持热加载
  • 集成 ESLint
  • 支持各种样式预处理器: SASS、LESS、 Stylus 等等
  • 支持 HTTP/2 推送

# 安装

npx create-nuxt-app <project-name>

# 目录结构

// 小型项目
- assets // 资源目录
- components // 组件
- pages // 页面
- static // 静态文件,会被编译到服务端根目录
- nuxt.config.js // 配置文件
- package.json
// 大型项目可能还需要
- content
- layouts // 布局
- middleware // 中间件
- modules
- plugins // 插件
- store // 状态管理

# 命令

# generate

如果想在发布时遇到错误停止,那么添加参数:--fauil-on-error

# 打包

  1. 使用nuxt build打包后的文件中不包含ssr内容,只有在nuxt start后,html源码才算是真正的ssr
  2. nuxt build --spanuxt generate很像,配置动态路由时都需要配置generate中的router

# nuxt.config.js

nuxt默认配置有时不会同步到文档,可以去github (opens new window)查看最新配置文件

# plugin

配置中如果为对象形式,ssr:true表示服务端和客户端都执行,plugin执行顺序,自上到下。

plugins: [
    {
      src:'~/plugins/axios',
      ssr:true // 默认为true,会同时在服务端(asyncData({$axios}))和客户端(this.$axios)同时拦截axios请求,设为false就只会拦截客户端
    }
  ]

如果使用的插件使用了es6语法,那么需要对其进行转义才能使用,否则会报错:

build:{
  transpile:['vue-tooltip']
}

# middleware

# extendRoutes

扩展路由,想动态添加可以:

export default {
  router:{
    extendRoutes:(routes,resolve)=>{
      routes.push({
        name:"hahaha",
        path:'/hahaha/:id',
        component:resolve(__dirname,'pages/detail/_id.vue')
      })
    }
  }
}

全面接管路由:

export default {
  router:{
    extendRoutes(routes, resolve){
     return [
       {
         name:"home",
         path:"/",
         component:resolve(__dirname,'pages/index'),
         meta:{
           title:"home"
         }
       }
       //...这里还可以继续写,一般如果要接管约定式路由的话,都会把它放到一个文件再引入
     ]
    }
  }
}

# loading

loading可以指定为自定义组件:

export default {
  loading: '~/components/LoadingBar.vue'
}

LoadingBar :


<template>
  <div v-if="loading" class="loading-page">
    <p>Loading...</p>
  </div>
</template>
<script>
  export default {
    data: () => ({
      loading: false
    }),
    methods: {
      start() {
        this.loading = true
      },
      finish() {
        this.loading = false
      }
    }
  }
</script>
<style scoped>
  .loading-page {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(255, 255, 255, 0.8);
    text-align: center;
    padding-top: 200px;
    font-size: 30px;
    font-family: sans-serif;
  }
</style>

# vuex

开启vuex方法:root 目录下新建 store 文件夹,按照目录自动生成。

export const state =>{}export const getters = {}
export const actions = {}
export const mutations = {}

# 生命周期

validate => asyncData => fetch => head

# context 上下文

存在于asyncData、plugins、middleware、nuxtServerInit,根据不同的环境,会提供不同的变量:

function (context) {
  // 一直可访问
  const {
    app,
    store,
    route,
    params,
    query,
    env,
    isDev,
    isHMR,
    redirect,
    error,
   $config
  } = context
  // 只有服务端可访问
  if (process.server) {
    const { req, res, beforeNuxtRender } = context
  }
  // 只有客户端可以访问
  if (process.client) {
    const { from, nuxtState } = context
  }
}

# app

appcontext 中最重要的属性,就像我们 Vue 中的 this,全局方法和属性都会挂载到它里面。因为服务端渲染的特殊性,很多Nuxt提供的生命周期都是运行在服务端,也就是说它们会先于 Vue 实例的创建。因此在这些生命周期中,我们无法通过 this 去获取实例上的方法和属性。使用 app 可以来弥补这点,一般我们会把全局的方法同时注入到 thisapp 中,在服务端的生命周期中使用 app 去访问该方法,而在客户端中使用 this,保证方法的共用。

# helper

# $nuxt

在客户端可以通过 window.$nuxt 获取,可以通过isOfflineisOnline检查网络连接状况

# 刷新当前页面

this.$nuxt.refresh(),该命令会刷新data和出发asyncData和fetch

# asyncData服务端异步请求数据(pages)

asyncData不可以用在组件上 asyncData只在首屏被执行,如果是这时通过页面进入其他页面,比如详情页,那么详情页的数据还是在客户端执行接口调用。

# fetch

控制组件数据渲染,使用fetch可以帮助组件在服务端完成数据获取。执行时机可能为服务端或者客户端,可以用在组件上,需要返回promisenuxt.js会等待promise完成后再渲染组件

export default {
  fetch ({ store, params }) {
    return axios.get('http://my-api/stars')
    .then((res) => {
      store.commit('setStars', res.data)
    })
  }
}

# watchQuery

监听参数字符串的更改,如果发生变化,将调用所有组件方法,设置该属性的话,可以在使用浏览器前进后退时调用接口。

# pageTransition

修改页面级别的路由切换动画

# middleware

执行顺序,nuxt.config => layout => page

# plugin

plugin是挂载全局方法的主要途径,

export default function (context, inject) {}

第二个参数inject可以将变量同时注入到content、vue、vuex

export default ({ app }, inject) => {
  inject('myInjectedFunction', string => console.log('That was easy!', string))
}

注意:应该始终保证plugin中的逻辑处理放在导出函数中,这样会保证plugin的执行顺序

# modules

# 编写自己的插件

nuxt配置中,modules数组中的配置和顶级配置都会被传到this.options.axios

export default {
  modules: [['@nuxtjs/axios', { anotherOption: true }]],
  // axios module is aware of this by using `this.options.axios`
  axios: {
    option1,
    option2
  }
}

# generate

# 禁用生成路由名文件夹

修改生成静态文件目录结构,假如有页面login,生成的结构为login/index.html,如果不想要外部的文件夹直接使用路由名作为 html 名称,那么设置

 generate: {
    subFolders: false,
  },

# exclude

config中设置exclude可以阻止与之匹配的路有,可以配合fallback一起使用,这时会以spa形式访问未生成的路有

# validate 参数验证(pages)

可以做页面级别的参数验证,通过return true或者false表示验证成功失败

# layouts

nuxt 布局分为 3 类:

  • default:默认使用的布局,会作用于所有没有指定 layout 的 page
  • error:error 算作页面,负责错误处理
  • 其他布局:需要指定 layout:'xxx',不能指定 error 为布局

layouts 非必须项,可以不用添加

# 使用

创建布局,一定要包含Nuxt组件,Nuxt组件只能使用在layout中:

<template>
  <div>
    <div>My blog navigation bar here</div>
    <Nuxt /> 
  </div>
</template>
export default {
  layout: "xxx",
};

# 错误页面

当有错误发生时,显示 error 页面(不会在 ssr 触发),虽然error.vue是放在 layouts,但是应当当作 page 来对待。

# 模版

通过在根目录下创建app.html来创建模版,通常更推荐使用nuxt.config.js配置额外内容,除非有特殊需求。

<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
</html>

# 路由

Nuxt.js会依据 pages 目录中的所有 *.vue 文件生成应用的路由配置。默认nuxt的路由都为异步加载。使用静态生成时动态路由不会被生成,需要配置generate

# 导航

通过NuxtLink组件在页面之间跳转,可以看做是RouteLink的替换,所有的 pages 应该都使用 NuxtLink 跳转,其他的外部页面应该使用a标签

# 插件

vue应用程序执行之前,运行的 js 插件

# 插件编写

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

# 启用插件

// nuxt.config.js
module.exports = {
  plugins: ['~/plugins/vue-notifications']
}

# ES6 插件

如果是插件是在node_modules的话,需要预先编译才能使用

module.exports = {
  build: {
    transpile: ['vue-notifications']
  }
}

# process

'process.env.NODE_ENV': ,
'process.mode': ,
'process.dev': ,
'process.static': ,
'process.target': ,
'process.env.VUE_ENV': ,
'process.browser': ,
'process.client': ,
'process.server': ,
'process.modern':

# 多语言i18n

nuxt/i18n

# SEO

  1. 配置 page head

# cache

# 页面缓存

理论上,如果页面缓存了那么就不需要接口和组件缓存

参考:浅谈 vue 前端同构框架 nuxt 及其性能优化 (opens new window)

# 日志

使用nuxt-winston-log,默认在根目录下自动创建logs文件夹,然后根据NODE_ENV创建 log 文件,例如:./logs/{NODE_ENV}.log

# 调试

添加eruda

export default function ({ app, route, isDev }) {
  // 限制:query中包含debug=true,或者域名开头包含dev
  if (
    /debug=true/.test(window.location.href) ||
    window.location.href.split('//')[1].startsWith('dev')
  ) {
    const debugReset = () => {
      window.ny_debug = {
        count: 0,
        startTime: null,
        endTime: null,
      };
    };
    debugReset();
    const addConsole = () => {
      const s = document.createElement('script');
      s.type = 'text/javascript';
      s.defer = true;
      s.onload = () => {
        // 本地开发默认显示
        if (isDev && window.eruda) window.eruda.init();
      };
      s.src = 'https://cdn.bootcdn.net/ajax/libs/eruda/2.4.1/eruda.min.js';
      document.body.appendChild(s);
    };
    // 添加eruda
    addConsole();
    // 限制:时间间隔小于3秒,点击区域y轴<=300,点击次数等于10
    document.body.addEventListener('click', (e) => {
      // 不满足条件时清空flag
      if (
        e.y >= 300 ||
        (window.ny_debug.startTime &&
          window.ny_debug.endTime &&
          window.ny_debug.endTime - window.ny_debug.startTime >= 3000)
      ) {
        debugReset();
      }
      window.ny_debug.count += 1;
      if (!window.ny_debug.startTime) {
        window.ny_debug.startTime = Date.now();
      } else {
        window.ny_debug.endTime = Date.now();
      }
      // 点击次数大于10
      if (window.ny_debug.count >= 10) {
        debugReset();
        // 存在eruda则销毁
        if (window.eruda._isInit) {
          window.eruda.destroy();
        } else {
          window.eruda.init();
        }
      }
    });
  }
}

# 错误处理

通过 context.error 触发错误跳转到 error 页面

async asyncData(context) {
    const id = context.params.id
    try {
      // Using the nuxtjs/http module here exposed via context.app
      const post = await context.app.$http.$get(
        `https://api.nuxtjs.dev/posts/${id}`
      )
      return { post }
    } catch (e) {
      context.error(e) // Show the nuxt error page with the thrown error
    }
  }

# 最佳实践

  1. 如果服务器已经开启了 gzip 压缩,那么禁用 nuxt 的压缩:compressor:false

  2. plugin最好都放到函数中去执行,保证插件执行顺序。

  3. 优化打包速度可以尝试安装这个包:https://github.com/harlan-zw/nuxt-build-optimisations,不一定有效

  4. 加速开发模式冷启动速度,安装nuxt-esbuild-module这个包,使用esbuild加速编译。使用vant时会导致组件错乱问题。

# 问题

# ssr和sag和spa如何选择?

# ssr

优点:如果有动态路由那么选择ssr,如果需要实时更新的数据选择ssr,

缺点:ssr无法获取客户端信息。

# ssg

优点:成本低,不需要服务和运维

缺点:static模式无法跨页面传递数据。

# spa

优点:spa跳转体验好

缺点:无seo,首屏慢

# vuex修改无效?

测试放在beforeMount中无效,但是放在mounted中执行可以生效,暂时不知道原因。

# middleware和plugin的区别?

  1. middleware肯定是基于请求的,而plugin不一定是。
  2. plugin可以全局注入
  3. plugin不能写在页面或者组件中
  4. plugin执行时机在服务端早于middleware,客户端晚于middleware

# 如何区分page还是component?

官方推荐方式是为每个page页面继承于一个特定组件:

import PageComponent from '~/components/Page.vue'
export default PageComponent.extend({
  // asyncData
  // ...
})

github链接 (opens new window)

还有一种方法可以判断:通过为每一个页面设置isPage属性,然后在mixin中进行判断和操作

// 页面
export default{
  meta: {
      showNavBar: false,
      showTabBar: true,
      isPage:true
    },
} 
// mixins plugin
import Vue from 'vue'
import locale from '@/assets/locale'
export default function ({ store, route, app }) {
  if (!Vue.__my_mixin__) {
    Vue.__my_mixin__ = true
    const pageTitle = {
      beforeCreate() {
        // 只处理page,过滤component
        if (this.$options?.meta?.isPage) {
          const pageTitle =
            app.i18n.t(this.$options.pageTitle) ||
            locale[store.state.locale][this.$options.name]?.pageTitle ||
            ''
          store.commit('setState', { pageTitle })
        }
      },
    }
    Vue.mixin(pageTitle)
  }
}

# nuxt generate 如何修改为相对路径

如果部署到服务的/static/html目录,那么修改router.base:

router: {
  base: "/static/html/";
}

这样打包出来的代码就会映射到/static/html/

# nuxt 如何同时使用ssr和ssg?

  1. 添加layout,对不使用ssr的页面添加client-only
  2. 使用路由中间价,修改是否spa,https://blog.lichter.io/posts/nuxt-dynamic-ssr-spa-handling/

# 如何修改路由切换动画?

添加一个全局样式,实现这几个类:

  • page-enter-active
  • page-leave-active
  • page-enter
  • page-leave
.page-enter-active, .page-leave-active{
    transition: opacity 1.5s;
}
.page-enter, .page-leave-active{
    opacity: 0;
}

# 路由守卫?

使用middleware,局部后置守卫使用beforeRouteLeave

# 无法在mounted中访问refs?

nuxt/components v2中组件默认为懒加载,导致无法访问,需要设置loader:true ,问题链接 (opens new window)

components: {
    loader:true,
    dirs: [
      { path: '~/components/display/', prefix: 'ny' },
      { path: '~/components/form/', prefix: 'ny' },
      { path: '~/components/basic/', prefix: 'ny' },
      { path: '~/components/icon/', prefix: 'icon' },
      { path: '~/components/navigation/', prefix: 'ny' },
    ],
  },

# 页面跳转后不会滚动到顶部?

https://g.yuque.com/yunwuxin/useful-note/8d719afc-b5a4-4a06-a6c7-c7842f3faba2?language=zh-cn

# Can't resolve 'fs'?

修改nuxt.config.js:

build:{
  extend(config, { isDev, isClient }) {
      config.node = {
        fs: 'empty'
      }
    }
}