---
# System prepended metadata

title: nuxt

---

# nuxt
{%hackmd BJrTq20hE %}
## 建立專案：
```javascript=
npx create-nuxt-app <项目名>
npx nuxi init <檔名> //(nuxt 3)
```
### 路徑快捷：
| 別名(ALIAS) | 目錄(DIRECTORY) |
|-----------|---------------|
| ~ or @    | srcDir，原始碼根目錄 |
| ~~ or @@  | rootDir，專案根目錄 |

### 目錄規則：
-   assets: 放需要 webpack 編譯的靜態資源
-   static: 不需要編譯的靜態資源
-   pages: 各頁對應的頁面元件 (相當於你寫 SPA 時，VueRouter路由指定的元件)
-   components: 跨頁面的元件，不具狀態
-   nuxt.config.js: Nuxt 全域設定檔
-   .nuxt: Nuxt 暫存資料夾
## components加入子資料夾( subfolder )：
```
├── assets
├── components
│   └── form
│       ├── FormGroup.vue
│       ├── TextField.vue
│       ├── Button.vue
```
其組件名稱為 <FormTextField /> and <FormButton />, 而不是 <FormGroup />

## nuxt-child(Router-view)巢狀視窗：
```
pages/
    --| index.vue (首頁放nuxt-child)
    --| index/
        --| index.vue (首頁會預設顯示此頁)
        --| login.vue (網址為:http://localhost:3000/login)
        --| parent.vue (多重巢狀-放nuxt-child)
        --| parent/ (建立一個同名資料夾，網址為:http://localhost:3000/parent)
            --| index.vue (要顯示的首頁一律叫index)
            --| children2.vue
```
## nuxt部屬&環境相關設定
- 環境參數設置
    -   当使用 `nuxt` 命令时，`dev` 会被强制设置成 `true`( 開發模式 )
    -   当使用 `nuxt build`， `nuxt start` 或 `nuxt generate` 命令时，`dev` 会被强制设置成 `false`( 生產模式 )
    ```javascript=
    module.exports = {
      dev: process.env.NODE_ENV !== 'production'
    }
    ```
- nuxt部屬路徑修改
    ```javascript=
    //nuxt.config.js
    target: 'static', //使用generate必加(預設是server)
    router: {
        mode: 'hash', // 使用 'hash' 主要是為了適配以相對路徑開啟的靜態站點， 必須使用 'hash' 否則路由跳轉不生效
        base: process.env.NODE_ENV==='production'?'./':'/',
        // 使用 './' 主要是為了適配以相對路徑開啟的靜態站點(生產模式)
    }
    ```
- Github page 會自動過濾掉某些開頭名字的資料夾
[如果檔案對應的路由，會發現對應的位子沒錯，但卻發生404?](https://medium.com/joelifestory/nuxt-js-%E9%9D%9C%E6%85%8B%E7%B6%B2%E7%AB%99%E6%8E%A8%E9%80%81%E5%88%B0-github-page-6004a70f83c4)
By default, Jekyll doesn’t build files or folders that:
    - are located in a folder called /node_modules or /vendor
    - start with _, ., or #
    - end with ~
    - are excluded by the exclude setting in your configuration file

    解決方式：
    ==在根目錄加上名為 `.nojekyll` 的空白檔案==
    
    加上去之後可以解決 github pages 
    讀不到 _framework, _content
    這幾個資料夾中的檔案
- npm run generate錯誤
    `DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.`
    [解決方法](https://github.com/nuxt/nuxt.js/issues/1552#issuecomment-341729165)
    ```javascript=
    //nuxt.config.js
    generate: {
      minify: {
        collapseWhitespace: false
      }
    }
    ```
- `push-dir`使用套件來佈署：
    https://nuxtjs.org/deployments/github-pages#command-line-deployment
    1. `npm i -D push-dir`
    2. 新增deploy指令：
        ```javascript=
        "scripts": {
          ...
          "deploy": "push-dir --dir=dist --branch=gh-pages --cleanup"
        },
        ```
    1. 
        ```javascript=
        yarn generate
        yarn deploy
        ```
## 安裝sass：
- 指定 @10 版本是因為它與 Nuxt 中使用的 webpack 版本兼容。
- Fiber 自動啟用與 sass 的同步編譯（速度提高 2 倍）
- @nuxtjs/style-loaders 確保您的變量和 mixin 在您的所有組件中都可用，而無需在每個文件中>導入它們。
- `-S`：`--save`的简写，它是把安装包的名称和版本号存到到dependencies中，是在生产环境中要用到的。
- `-D`：`--save-dev`標誌確保這些包不會出現在最終構建中，從而使我們的項目規模盡可能小。由於 SCSS/Sass 在構建時被編譯為標準 CSS，因此不需要這些。
1. `npm install --save-dev sass node-sass sass-loader@10`
2. 加入資源資料夾及檔案：
    ```
    assets
        |-scss
        |-css
    ```
3. 添加scss路徑如下：
    ```javascript=
    css: [
        { src: '~/assets/scss/all.scss', lang: 'sass' }
    ],
    ```
## 啟用vite模式：
`npm i -D nuxt-vite`

設定：
```javascript=
// nuxt.config
export default {
    buildModules: [
        'nuxt-vite'
    ],
    vite: { ssr: true }, //vite啟用ssr
}
```
## 啟用composition api：[文件](https://composition-api.nuxtjs.org/getting-started/introduction/)
`npm install @nuxtjs/composition-api --save`
```javascript=
//nuxt.config.js
{
    buildModules: [
        '@nuxtjs/composition-api/module'
    ]
}

//引入的時候
[x] const { createApp, ref, onMounted, onUnmounted, computed} = Vue; //vue3才有內含
[O] import { useStore, useRoute, useRouter } from '@nuxtjs/composition-api'
```
## 啟用axios：
`npm install @nuxtjs/axios`
```javascript=
//nuxt.config.js
export default {
    modules: ['@nuxtjs/axios']
}
```
如使用vite mode，nuxt-vite需降版為0.2.4，否則會`cannot find module 'lib/axios' from...`
(PS：截稿此時最新版為0.3.5)

### 封裝axios
- 因為nuxt自己封裝過，所以無法使用`axios.defaults.headers.common['Authorization']`，所以要自己重新封裝

[參考文件](https://zhuanlan.zhihu.com/p/75864109)
1. 創建/api/request.js，加入攔截器

    ```javascript=
    /**
     * 封装Axios
     * 处理请求、响应错误信息
     */
     import { Message } from 'element-ui'  //引用饿了么UI消息组件
     import axios from 'axios' //引用axios
     import Cookies from 'js-cookie'

     // create an axios instance
     const service = axios.create({
       baseURL: 'http://114.33.243.242', // 所有异步请求都加上/api,nginx转发到后端Springboot
       withCredentials: true, // send cookies when cross-domain requests
       timeout: 5000 // request timeout
     })

     // request interceptor(攔截器)
     service.interceptors.request.use(
       config => {
         // do something before request is sent
         // config.headers['-Token'] = getToken()
         let token = Cookies.get('allwellToken')
         if (token) {
           config.headers['token'] = token
         }
         return config
       },
       error => {
         // do something with request error
         console.log(error) // for debug
         return Promise.reject(error)
       }
     )

     // response interceptor
     service.interceptors.response.use(
       /**
        * If you want to get http information such as headers or status
        * Please return  response => response
        */

       /**
        * Determine the request status by custom code
        * Here is just an example
        * You can also judge the status by HTTP Status Code
        */
       response => {
         const res = response.data //res is my own data

         if (res.code === 0) {
         // do somethings when response success
         //   Message({
         //     message: res.message || '操作成功',
         //     type: 'success',
         //     duration: 1 * 1000
         //   })
           return res
         } else {
           // if the custom code is not 200000, it is judged as an error.
           Message({
             message: res.message || 'Error',
             type: 'error',
             duration: 2 * 1000
           })
           console.log(res);
           return Promise.reject(new Error(res.message || 'Error'))
         }
       },
       error => {
         console.log('err' + error) // for debug
         Message({
           message: error.message,
           type: 'error',
           duration: 5 * 1000
         })
         return Promise.reject(error)
       }
     )

     export default service //导出封装后的axios
    ```
2. 創建api接口：/api/user.js

    ```javascript=
    import request from './request'

    export function apiUserLogin(data) {
      return request({
        url: '/api/core/userInfo/login',
        method: 'post',
        data: data
      })
    }

    export function apiUserCheckToken() {
      return request({
        url: '/api/core/userInfo/checkToken',
        method: 'get',
      })
    }
    ```
3. 使用：

    ```javascript=
    import { apiUserLogin } from '@/api/user.js'

    apiUserLogin(this.data)
        .then((res) => {
          console.log(res);
          if (res.code == 0) {
              ...
    ```
## 安裝bootstrap：
`npm install bootstrap`

Bootstrap5 有用到 Popper，另外安裝 `npm install @popperjs/core
`

自訂bootstrap：
```sass=
@import "node_modules/bootstrap/scss/functions";
@import "./helpers/_variables";
@import "./helpers/_utilities";
@import "node_modules/bootstrap/scss/bootstrap";
```
在plugins資料夾新增`boostrap.js`：
```javascript=
import * as all from '~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js'
export default all
```
設定：
```javascript=
//nuxt.config.js
export default {
    css: [
        { src: '~/assets/scss/all.scss', lang: 'sass' }
    ],
    //js要放在plugins裡面，放在header，webpack不會編譯header路徑
    plugins: [
        { src: '~plugins/bootstrap.js', mode: 'client' },
    ],
}
```
plugin不能直接引入`{ src: '~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', ssr: false}`，否則會出現警告：[9121](https://github.com/nuxt/nuxt.js/issues/9121)
```
 WARN  Found 2 plugins that match the configuration, suggest to specify extension:

- C:/Users/Allwell-dev02/Documents/vue/nuxt/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js
- C:/Users/Allwell-dev02/Documents/vue/nuxt/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js.map
```

## 安裝vee-validate("^3.4.14")：
[教學stackFlow](https://stackoverflow.com/questions/59485231/veevalidate-3-x-nuxt-js-push-a-custom-error-to-validationobserver)
[rules查詢](https://vee-validate.logaretm.com/v3/guide/rules.html#rules)

1. npm install vee-validate --save
2. 在根目錄建立plugins資料夾
3. plugins下創建vee-validate.js，引入
    ```javascript=
    import Vue from "vue";
    import { extend, ValidationObserver, ValidationProvider } from "vee-validate";

    //引入全部
    import * as rules from "vee-validate/dist/rules";
    for (let [rule, validation] of Object.entries(rules)) {
        // noinspection TypeScriptValidateTypes
        extend(rule, {
            ...validation
        });
    }

    //引入部分規則
    import { required, email, min, confirmed } from "vee-validate/dist/rules";
    extend("required", {
        ...required,
        message: "This field is required",
    });
    extend("email", email);
    extend("min", min);
    extend("confirmed", {
        ...confirmed, 
        message: "密碼不一致" 
    });

    Vue.component("ValidationProvider", ValidationProvider);
    Vue.component("ValidationObserver", ValidationObserver);
    ```
4. 在nuxt.config.js設定：
    ```javascript=
    plugins: [
        { src: '~/plugins/vee-validate.js', ssr: false }
    ],
    ```
5. 使用方式：沒用form
    ```htmlembedded=
    <template>
        <ValidationObserver tag="form" ref="form"> <!--tag可自訂標籤(optional)-->
            <!-- Tel input -->
            <ValidationProvider
            v-slot="{ errors }"
            rules="required|isPhone"
            name="tel"
            >
            <div class="form-outline mb-4">
              <input
                id="tel"
                v-model="data.tel"
                name="tel"
                type="text"
                class="form-control"
                :class="{ 'is-invalid': errors[0], 'is-valid': data.tel }"
              />
              <span class="invalid-feedback">{{ errors[0] }}</span>
              <label class="d-block form-label" for="registerName">手機號</label>
            </div>
          </ValidationProvider>
        </ValidationObserver>
    </template>
    ```
    ```javascript=
    const form = ref(null);
    const data = ref({
      id: 'abc',
      tel: '',
    })
    
    //自訂規則(須在該頁新增，不能在plugin定義，會找不到)：
    import { extend } from 'vee-validate'

    extend('isPhone', (value) => {
      const phoneNumber = /^(09)[0-9]{8}$/
      return phoneNumber.test(value) ? true : '電話號碼格式錯誤'
    })

    function submit() {
      // 送出表單
      const submit = form.value.validate()
      submit.then((isValid) => {
        if (isValid) {
          console.log('doSomething...')
        }
      }
    })
    ```
    密碼確認寫法：
    ```htmlembedded=
    <ValidationObserver tag="form" ref="form">
        <!--可當form標籤用or not-->
        <!-- password input -->
        <ValidationProvider v-slot="{ errors }" rules="required" name="pass">
            <div class="form-outline mb-4">
                <input
                    id="tel"
                    v-model="data.pass"
                    name="pass"
                    type="password"
                    class="form-control"
                    :class="{ 'is-invalid': errors[0], 'is-valid': data.pass }"
                />
                <span class="invalid-feedback">{{ errors[0] }}</span>
                <label class="d-block form-label" for="registerName">密碼</label>
            </div>
        </ValidationProvider>
        <ValidationProvider v-slot="{ errors }" rules="required|confirmed:pass" name="pass2">
            <div class="form-outline mb-4">
                <input
                    id="tel"
                    v-model="data.pass2"
                    name="tel"
                    type="password"
                    class="form-control"
                    :class="{ 'is-invalid': errors[0], 'is-valid': data.pass2 }"
                />
                <span class="invalid-feedback">{{ errors[0] }}</span>
                <label class="d-block form-label" for="registerName">密碼確認</label>
            </div>
        </ValidationProvider>
    </ValidationObserver>
    ```
## 安裝element-ui
[官方文件](https://element.eleme.cn/#/zh-CN/component/installation)
1. `npm install element-ui -S`
2. 在 plugins 文件夹下新建 `element-ui.js` 文件
    ```javascript=
    import Vue from 'vue'
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    import locale from 'element-ui/lib/locale/lang/zh-TW'

    Vue.use(ElementUI, { locale })
    ```
3. 配置 nuxu.config.js
    ```javascript=
    plugins: [
        '@/plugins/element-ui'
    ],
    ```
## optional api與composition api混用
[CSDN-link](https://blog.csdn.net/csl125/article/details/115712848)
```javascript=
<template>
  <div class="mt-l72">
    <p>{{ text1 }}</p>
    <button @click="myFun1">按钮1</button>
    <p>{{ age }}</p>
    <button @click="myfun2">按钮2</button>
  </div>
</template>

<script>
    import { ref } from "@nuxtjs/composition-api";

    export default {
      name: "App",
      data() {
        return {
          text1:  1,
        };
      },
      // setu函数是组合api的入口函数
      setup() {
        console.log("setUp函数被执行", this);
        let age = ref(20);
        function myfun2() {
            age.value+=1
        }
        //optional api可以接到return出去的值
        return { age, myfun2 };
      },
      beforeCreate() {
        console.log("beforeCreate被执行", this);
      },
      created() {
        console.log("Create被执行", this);
      },
      components: {},

      methods: {
        myFun1() {
          this.age+=1
          this.text1 +=2
        },
      },
    };
</script>
```
注意：
- 只有 option api 引用 composition api 的份，没有反过来的份；
- 而且由于 composition api 立即执行并 return 的原因，它不被允许作为 async 异步函数进行定义，只能是同步不能是异步。
- setup函数无法使用data和methods，setup函数中this为undefined

//{%hackmd 05Dx8Q4HTa6H23X9PNrFqQ %}
## middleware(nuxt的路由守衛)
[Ray的blog](https://hsiangfeng.github.io/vue/20190816/3627128539/)
1. 在根目錄下新建 `middleware` 資料夾，然後新增 `routerAuth.js`
    ```javascript=
    export default ({ route, redirect }) => {
      console.log('此為路由守衛');
      console.log(route.meta[1].requiresAuth) //由某頁面導入的參數
      if (route.meta[1].requiresAuth) {
        let authStates = false; // 改為 true 就可以進入了
        //驗證中...
        if (!authStates) { // 若沒有登入就使用返回登入頁面
          return redirect('/login')
        }
      }
    }
    ```
2. 在需要守衛的頁面加上
    ```javascript=
    export default {
      middleware: 'routerAuth', //導向哪一隻路由守衛
      //要導入的參數(optional)
      meta: {
        requiresAuth: true,
      },
    }
    ```
    如果要全頁面驗證，直接在 `nuxt.config.js` 設置
    ```javascript=
    export default {
      middleware: []
      /* 單數亦可用字串 */
      /* middleware: 'stat' */
    }
    ```
## asyncData 方法 (真SSR)
可以在设置组件的数据之前能异步获取或处理数据。如圖： [官方文件](https://www.nuxtjs.cn/guide/async-data)
![](https://i.imgur.com/boD2y9k.jpg)
asyncData只是在首屏的时候调用一次，后续交互还是交给client处理
```javascript=
<template>
  <div class="mt-5">
    <h1>姓名：{{ info.userId }}</h1>
    <h1>年龄：{{ info.id }}</h1>
    <h1>兴趣：{{ info.title }}</h1>
    {{ name }}
  </div>
</template>
<script>
import axios from 'axios'
export default {
  data() {
    return {
      name: 'Hello World',
      info: ''
    }
  },
  async asyncData() {
    // 此區塊在nuxt渲染的時候會先在此等待，等資料回傳回來之後再一併渲染(html+資料)
    let { data } = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
    console.log(data);
    return { info: data } //return回去的数据会直接和data(){return {}}放在一起
  },
  created() {
    // 如果只用一般axios，nuxt渲染不會等資料回傳就已經跑完渲染了(只有html結構)
    axios.get('https://jsonplaceholder.typicode.com/todos/1').then((res) => {
      console.log(res);
      this.info = res.data
    })
  }
}
</script>

```
## SEO、meta設置
個性meta設置：比如你現在要作個新聞頁面，那爲了搜索引擎對新聞的收錄，需要每個頁面對新聞都有不同的title和meta設置。 [教學](https://www.twblogs.net/a/5c015e09bd9eee7aec4ea830)
1. 使用鏈結傳遞title等參數
    ```java=
    // /pages/news/index.vue
    <li><nuxt-link :to="{name:'news-id',params:{id:123,title:'nuxt.com'}}">News-1</nuxt-link></li>
    ```
2. 接著修改/pages/news/_id.vue，讓它根據傳遞值變成獨特的meta和title標籤。
    ```javascript=
    // /pages/news/_id.vue
    <template>
      <div>
          <h2>News-Content [{{$route.params.id}}]</h2>
          <ul>
            <li><a href="/">Home</a></li>
          </ul>
      </div>
    </template>
    <script>
    export default {
      validate ({ params }) {
        // Must be a number，使用正則驗證參數，返回true正常進入頁面，如果返回false進入404頁面。
        return /^\d+$/.test(params.id)
      },
      data(){
        return{
          title:this.$route.params.title,
        }
      },
    //獨立設置head信息
      head(){
          return{
            title:this.title,
            meta:[
              {hid:'description',name:'news',content:'This is news page'}
            ]
          }
        }
    }
    </script>
    ```
    注意：爲了避免子組件中的meta標籤不能正確覆蓋父組件中相同的標籤而產生重複的現象，建議利用 hid 鍵爲meta標籤配一個唯一的標識編號。
## vuex in nuxt
只能搭配option api