Nuxtjs 超入門

Stephen Lai
16 min readJul 2, 2020

VueJs 開發者必須會的全端框架

Nuxt.js 的精華在提供單頁式應用程序(SPA) SSR (Server Side Rendering)的功能, 也就是在後端預先渲染網頁, 再回傳到前端顯示. 如果用 vue-cli 或者引入 vue.js cdn 的方式開發, 在網頁上按滑鼠右鍵檢查網頁原始碼只會看到 <div id=”app”></div> 空白內容. 這樣對於搜尋引擎爬文非常的不友善. Nuxtjs 就是為了解決這個問題而生的 Vuejs 框架, 並且整合了後端功能例如 expressjs 可以讓我們開發全端產品.

Nuxtjs 的優點如下

  • 整合了 vuex, vue router, 和 vue-meta, 不需要做額外設定就可以直接使用. 比如 router, 只要將頁面放在 pages 資料夾內自動生成路由, 不需要寫手動設置路由
  • 內建的資料夾讓我們可以很好的規畫專案內的檔案, 每個資料夾都有特定功能, 比如在 pages 資料夾的組件會自動生成路由; layout 資料夾下設置 layout 組件; assets 資料夾內放置全域檔案如 css 樣式, 圖片, 聲音, 文字檔
  • Nuxtjs 有以下功能資料夾
    - assets : 存放 css, 圖片, 聲音, 文字檔
    - components : 存放會重複使用到的組件
    - layouts : 存放默認 layout 以及自定義 layout
    - middleware : 存放中介軟體 (函示)
    - pages : 存放頁面組件
    - plugins : 設定第三方插件或自訂義插件
    - static : 和 assets 資料夾功能相同, ~官方不推薦使用~
    - store : Vuex 檔案存放區
  • 對搜尋引擎非常友善, NuxtJs 提供的 SSR 讓 Vuejs SPA 很容易被搜尋引擎找到, 這是用 vue-cli 或者引入 cdn 開發辦不到的, 這也是 nuxtjs 最大的特色
  • 使用 universal 或者 static 模式會先在後端預先渲染好網頁, 讓 nuxtjs app 在第一次向後端請求頁面快速得到渲染好的畫面, 補足 spa 模式第一次載入時要花費很長時間的缺點

開發 Vuejs 專案重點摘要

父組件傳屬性給子組件

在 vue-cli 或 nuxtjs 專案中建立組件, 決定那些屬性要由外部(父組件)傳入, 例如在 PostPreview.vue 組件中 設置 id, title, thumbnail, previewText 四個屬性

在 <script> 設置 props 屬性,

<script>
export default {
props: {
id: {
type: Number,
default: 0,
required: true,
validator(value) {
// 如果傳入的值小於 10, console 印出 'validate vaule' 字樣; 否則印出 'illegal value' 字樣
const message = (value <= 10) ? 'legal value' : 'illegal value'
console.log(message)
}
},
title: {
type: String,
required: true
},
previewText: {
type: String,
required: true
},
thumbnail: {
type: String,
required: true
}
}
}
</script>
設置 props 屬性相當於用 typescript 設置每個變數的資料格式, 考慮相當周全

在 <template> 渲染出來

<template>
<nuxt-link :to="`/posts/${id}`" class="post-preview">
<article>
<div class="post-thumbnail"
:style="{backgroundImage: `url(${thumbnail})`}"
>
</div>
<div class="post-content">
<h1>{{ title }}</h1>
<p>{{ previewText }}</p>
</div>
</article>
</nuxt-link>
</template>

父組件 Post.vue 傳 props 給子組件 PostPreview, 注意: 如果傳入的不是字串, 屬性欠要加上 ‘:’ 符號, 例如 :id=”12" 等於傳入值 12; 如果 id=”12" 等於傳入字串 ‘12’

<template>
<div>
<PostPreview
:id="1"
title="This is Post 1"
thumbnail="https://pixel.com/images/1"
previewText="This is first post!"
>
<PostPreview
:id="2"
title="This is Post 2"
thumbnail="https://pixel.com/images/2"
previewText="This is second post!"
>
<PostPreview
:id="3"
title="This is Post 3"
thumbnail="https://pixel.com/images/3"
previewText="This is third post!"
>
</div>
</template>

設置活躍菜單(active link) 樣式

在 Nuxtjs 專案中如果我們希望點選到的菜單(menu)維持樣式(例如文字變成紅色, 滑鼠點選網頁任何地方樣式都不會消失), 在 style 加入下列代碼

a:hover,
a:active,
a.nuxt-link-active {
color: red;
}

讓元素動起來

在 Nuxtjs 專案中要讓元素產生動畫, 方法和 vuejs 一樣, 用 <transition name=”動畫名稱”> 標籤把元素包起來, 然後再組件的作用域樣式(scoped style)內設定動畫內容. 假設我們要做一個左右滑動(slide-side)效果

<template>
<transition name="slide">
<h1>This is title</h1>
</transition>
</template>
<style scoped>
// 滑入後和滑出前的狀態(x=0)
.slide-side-enter-active,
.slide-side-leave-active {
transition: transform 0.3s ease-out;
}
// 滑入前和滑出後的位置在(x=-100%)
.slide-side-enter,
.slide-side-leave-to {
transform: translateX(-100%);
}
</style>

link(連結)

  • 在 html 檔案中連結用 <a href="路徑"></a>表示, 點擊連結會發送請求到服務器, 服務器會根據請求發送渲染好的網頁到前端.
  • 在 vue-cli 專案中連結用 <router-link to="路徑"></router-link>表示
  • 在 nuxtjs 專案中連結用 <nuxt-link to="路徑"></nuxt-linke> 表示
  • 也可以用 javascript 執行連結, 在函式內加入 this.$router.push(`/users/${id}`) 切換到指定使用者頁面, vue-cli 和 nuxtjs 專案皆通用

建立 Global CSS 檔案

  • 在 assets 資料夾下建立 `style/main.css`
  • 在 `nuxt.config.js` 的 css 陣列內引入
    css: [ '~/assets/style/main.css’]

加入客製化 Error Page

  • 在 layoutout 資料夾下建立 error.vue
  • 在 <template> 導向特定頁面用 <a> 因為產生錯誤 <nuxt-link> 不能正常動作

加入 Google Fonts

  • 在 nuxt.config.js 的 head 物件內加入
    head: {
    link: [ { rel: ‘stylesheet’, href: ‘https://fonts.googleapis.com/css?..’ } ]
    }
  • 然後在 component, layout 引入

加入靜態資產(圖片, 聲音, 影片)

  • 在 assets 資料夾下加入 images/xxx.jpg
  • 組件內引入 ‘~assets/images/xxx.jpg’

加入客製化 layout

  • 在 layout 資料夾下建立 admin.vue
  • 在組件內引入 export default { layout: ‘admin’ }

asyncData(context) { }

  • 在 client 和 server 端作用, 可以用 console.log() 檢查前後端 console 輸出信息
  • 處理 Server-Side 傳過來的資料,
  • asyncData 在組件還沒渲染前作用, 所以 this 不能用, 必須用 context 存取屬性; 比如 this.$route.params.id 要改為 context.route.params.id
  • 只能作用在 pages 資料夾下, components, layout 都不能用 !!!

範例: 在 asyncData() 內讀取 firebase 資料

import axios from 'axios'asyncData(context) {
return axios
.get(`https://nuxt-blog.firebaseio.com/posts/${context.params.postId}.json`)
.then(res => return { loadedPosts: res.data }})
.catch(e => context.error())
}

Vuex Store

  • 在 store 資料夾建立 index.js
  • 有兩種模式: Classic Mode, Modules Mode

fetch(context) { }

  • 只作用在 pages 資料夾內
  • fetch 到 vuex store 的資料要用 computed 讀取

nuxtServerInit(vuexContext, contect) { }

在 store 內的 actions 物件內加入 actions: { nuxtServerInit(vuexContext, context) { }

在 nuxtServerInit() fetch 遠端 api 資料, 例如從 firebase 讀取資料

actions: {
nuxtServerInit(vuexContext, context) {
return axios.get('https://nuxt-blog.firebaseio.com/posts.json')
.then(res => {
const postsArray = []
for (const key in res.data) {
postsArray.push({ ...res.data[key], id: key }]
}
vuexContext.commit('setPosts', postsArray)
})
.cactch(e => context.error(e))
}
}

非常重要的檔案 ~ nuxt.config.js

nuxt.config.js 屬性作用於全域

  • mode : 可以設定 ‘universal’ 或 ‘spa’ 模式
  • head : 設定 html 的 <head>, 可以被 pages 下的路由組件覆寫
head: {
title: '',
meta: [
{ name: '', content: '' },
{ hid: '', name: '', content: '' }
],
link: [
{ rel: '', type: '', href: '' }
]
}
  • loading : 客制化進度條 (progress-bar)
// 設置進度條顏色, 高度, 時間
loading: { color: '#fa923f', height: '4px', duration: 5000 }
// 禁用進度條
loading: false
  • css : 設置全域 css 檔案
// assets/styles 資料夾下的 main.css 適用於所有組件, '~' 符號後不須加上 '/', 直接連接資料夾名稱css: [  
'~assets/styles/main.css'
]
  • env : 設置環境變數
// 組件非同步存取遠端資料可以用 process.env.baseUrl 取代寫死的網址
env: {
baseUrl: process.env.BASE_URL || 'https://nuxt-blog.firebase.com'
}
  • rootDir : 設置根路徑
// 默認路徑
rootDir: '/'
// 設置 my-app 為根目錄
rootDir: '/my-app/'
  • router :
  • srcDir : 設置專案路徑
// 設置專案在 client-app 路徑下
srcDir: 'client-app/'
  • transition : 設置切換頁面時的漸變效果
transition: {
name: 'fade', // css 檔案中 class 的開頭名稱, 相當於 <transition name="fade">
mode: 'out-in' // 漸變模式, page 先 fadeOut, 然後 fadeIn
}

在 assets/styles/main.css 加入下列代碼

.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
  • plugins : 全域設置常用功能 (例如自訂義或者第三方組件), 使用該組件不需要被引入, 直接調用即可

範例一 : 在 plugins 資料夾建立 core-component.js, 加入下列代碼

import Vue from 'vue'import AppButton from '@/components/UI/AppButton'Vue.component('AppButton', AppButton)

範例二 : 在 在 plugins 資料夾建立 date-filer.js, 加入下列代碼

import Vue from 'vueconst months = [
'Janaury', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
]
const dateFilter = value => {
return formatData(value)
}
function formatData(inputDate) {
const date = new Date(inputDate)
const year = date.getFullYear()
const month = date.getMonth()
const day = date.getDate()
const formattedDate = `${day}. ${months[month] ${year}}`
return formattedDate
}
Vue.filter('date', dateFilter)

在 nuxt.config.js 註冊 <AppButton> 和 dateFilter

plugins: [
// 方法一 : 直接引入路徑
'~plugins/core-components.js',
'~plugins/date-filter.js'
// 方法二 : 如果方法一行不通, 用方法二
{ src: '~plugins/core-components.js', ssr: false }
{ src: '~plugins/date-filter.js', ssr: false }
]

組件中直接調用 <AppButton />, 不需要額外引入

在日期後面加上 | date 就可以使用 dateFilter

  • modules : 設置第三方程式庫

首先安裝套件, 例如安裝 axios

在 nuxt.config.js 加入下列程式碼

modules: [
'@nuxtjs/axios'
],
axios: {
baseURL: process.env.BASE_URL || 'https://nuxt-blog.firebase.com/',
credentials: false
}

在組件中調用 axios 不需額外引入, 直接使用即可, 例如在 methods 屬性中 使用 this.$axios. $get('/posts/') 或者在 asyncData 或 nuxtServerInit 中使用 context.app.$axios.$get(‘/posts/’)

第 8 節 : The Server Side

Adding Server Side Middleware

  • 在 nuxt.config.js 加入下列代碼
const bodyParser = require('body-parser')serverMiddleware = [
bodyParser.json()
'~/api'
]
  • 在 root directory 下建立 api/index.js
  • 安裝 express, body-parser : npm i express body-parser
  • 在 api/index.js 加入下列代碼
const express = require('express')
const router = express.Router()
// 轉換成 expressjs 格式
const app = express()
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request)
Object.setPrototypeOf(res, app.response)
req.res = res
res.req = req
next()
})
router.post('/track-data/', (req, res) => {
console.log('Store data', req.body.data)
res.status(200).json({ message: 'Success!' })
})
module.exporst = {
path: '/api',
handler: router
}

Testing the Middleware

asyncData() vs. fetch() vs. nuxtServerInit()

Nuxtjs 在組件還沒渲染前, 讀取後端資料, 傳到前端渲染頁面的行為稱為 Server Side Render (SSR) 或者 pre-render, 有 3 種方法可以做到, 分別是 asyncData, fetch 和 nuxtServerInit; asyncData 和 fetch 一樣, 只作用在 pages 資料夾內,

參考資料

[Nuxt.js : Vue.js on Steroids / Udemy](https://www.udemy.com/course/nuxtjs-vuejs-on-steroids/)

--

--