# Nuxt3配置入門
###### tags: `Nuxt`, `Nuxt3`
### SPA(Single Page Application)跟SSR(Server Side Rendering)的差別
- SPA應用:也就是單頁應用(切換頁面其實就是切換元件,始終都是在`index.html`檔案裏),這些多是在客戶端的應用,==不能進行SEO優化(搜索引擎優化)。==
- ![](https://i.imgur.com/dl0fVkL.png)
- SSR應用:在服務端進行渲染,渲染完成後返回給客戶端,**每個頁面有獨立的URL,++對SEO友好++。**(因為搜尋引擎能直接爬到在 HTML 上的內容)
- ![](https://i.imgur.com/Otix5of.png)
- SPA+SSR應用:在服務端進行渲染,**會回傳兩種檔案,一是完整的 HTML 檔案,二是跑 SPA 模式時會用到的檔案**,渲染完成後返回給客戶端後就會跑 SPA 模式。
- ![](https://i.imgur.com/KklNP9i.png)
### Nuxt3新特性
- 更輕量:以現代瀏覽器為基礎的情況下,**服務器部署和客戶端產物最多減小75倍。**
- 更快:用動態服務端代碼來優化冷啟動。
- Hybird:增量動態生成和其他高級模式現在都成為可能。
- Suspense: 在導航觸發的前後,皆可以在任何元件中取得數據資料。
- Composition API : 使用 Composition API 和 Nuxt 3 的 Composables 實現真正的程式碼可重用性。
- Nuxt CLI:全新的零依賴體驗,幫助你輕鬆建立專案與模組整合。
- Nuxt Devtools :專屬開發除錯工具,提供更多的資訊與快速修復,讓工作更高效。
- Nuxt Kit :具備基於 TypeScript 和跨版本兼容性的全新模組開發。
- Webpack5 : 更快的構建速度和更小的構建包,並且零配置。
- Vite:**使用 Vite 作為打包工具**,體驗閃電般快速的 HMR。
- Vue3 : **完全支持Vue3語法**
- TypeScript:由原生TypeScript和ESM構成,沒有額外配置步驟。
### 創建專案
```javascript=
npx nuxi init `project name`
```
### 默認目錄結構
```
- .nuxt // 自動生成的目錄,用於展示結果
- node_modules // 項目依賴包存放目錄
- .gitignore // Git的配置目錄,比如一些文件不用Git管理就可以在這個文件中配置
- app.vue // 項目入口文件,你可以在這里配置路由的出口
- nuxt.config.ts // nuxt項目的配置文件
- package-lock.json // 鎖定安裝時包的版本,以保證其他人在 npm install時和你保持一致
- package.json // 包的配置文件和項目的啟動調式命令配置
- README.md // 項目的說明文件
- tsconfig.json // TypeScript的配置文件
```
### 需要自己新增的目錄
```
-------------常用
- pages // 開發的頁面目錄
- components // 組件目錄
- assets // 靜態資源目錄
- layouts // 項目布局目錄
--------------
-------------其他
- composables // nuxt會自動導入vue中的組合式API
- layouts // nuxt提供了一種可定制的布局框架
- middleware // nuxt提供了一種可定制的路由中間件框架,可定制路由策略
- plugins // 插件目錄,nuxt將自動注冊插件
- public // 網站根目錄
- server // 服務端路由
- env // 全局環境變數
-------------
```
### [引入自訂義CSS](https://stackoverflow.com/questions/69953025/nuxt-3-resolver-resolvemodule-is-not-a-function)
> 只能全局引入一隻功能類型的(ex mixin or palette)scss檔案,但你可以把多個import在一隻檔案裡再一併引入
```javascript=
// in nuxt.config.ts
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
scss: {
// 全局提供 variables裡包含了mixin、palette
additionalData: '@import "~/assets/scss/_variables.scss";',
},
},
},
},
//全局css
css: [
"~/assets/scss/app.scss",
],
})
```
### [定義head](https://v3.nuxtjs.org/api/composables/use-head)
>Nuxt最重要也最方便的點就是可以依照不同頁面定義不同的meta或title、description
你可以每頁定義不同的meta、引入不同的字體或不同的套件等等...
```javascript=
const title = ref<string | any>('Index Page')
const description = ref<string>('Index Page Description')
useHead({
title,
meta: [{
name: 'description',
content: description,
}],
})
```
### [路由<pages 目錄>](https://v3.nuxtjs.org/guide/directory-structure/pages)
- 基礎路由
1. 在`pages`裡新增檔案就可以直接利用`NuxtLink`做跳轉
```javascript=
<NuxtLink to="/">Index Page</NuxtLink>
```
2. 在pages裡**創建跟父層檔案名一樣的資料夾**就可以做鑲套路由,超方便
```html=
//in nested.vue
<template>
<div>
<h1>Nested Page</h1>
<NuxtChild></NuxtChild>
<NuxtLink to="/nested/child">/nested/child</NuxtLink>
<NuxtLink to="/nested/child2">/nested/child2</NuxtLink>
</div>
</template>
```
```html=
//in nested/child.vue
<template>
<div>
<h1 >Child Page</h1>
<NuxtLink to="/nested">to Nested Page</NuxtLink>
</div>
</template>
```
2. [動態路由 適合用來做**重複性相當高**的頁面](https://v3.nuxtjs.org/guide/directory-structure/pages#dynamic-routes)
> 動態路由規則為 ~/pages/[[slug]]/index.vue or ~/pages/[[slug]].vue
```
-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue
```
- 基本用法
適合場景: 一頁有很多不同子頁,**標題跟內容都是根據api回傳**
1. 建立`user.vue`
2. 建立父層資料夾把上面的`users-[group]/[id].vue`包起來,變成`user/users-[group]/[id].vue`
3. user.vue頁如下:
```javascript=
// in user.vue
const route = useRoute()
const description = ref<string>('User敘述...')
useHead({
title: `${route.meta.title}`,
meta: [
{
name: 'description',
content: description,
},
{
name: 'og:title',
content: `App Name - ${route.meta.title}`,
},
],
})
definePageMeta({
title: 'User',
})
<template>
<div>
<h1 class="text-xl">
User Page(Dynamic params page)
</h1>
<NuxtPage :custom-props="..."/>
<div class="flex space-x-2">
<NuxtLink to="/user/users-admins/123" class="py-2 px-3 bg-cyan-500 text-white text-sm font-semibold rounded-md shadow-lg shadow-cyan-500/50 focus:outline-none">
Go /users-admins/123
</NuxtLink>
<NuxtLink to="/user/users-permission/123" class="py-2 px-3 bg-blue-500 text-white text-sm font-semibold rounded-md shadow-lg shadow-blue-500/50 focus:outline-none">
Go /users-permission/123
</NuxtLink>
<NuxtLink to="/user/users-settings/123" class="py-2 px-3 bg-indigo-500 text-white text-sm font-semibold rounded-md shadow-lg shadow-indigo-500/50 focus:outline-none">
Go /users-settings/123
</NuxtLink>
</div>
</div>
</template>
```
4. 子頁面透過`props`來刷新meta並透過`params`拿取資料
```javascript=
// user/users-[group]/[id].vue
const props = defineProps(['customProps'])
const { currentNews } = toRefs(props)
const description = ref<string>(props.customProps.description)
useHead({
title: `${props.customProps.title}`,
meta: [
{
name: 'description',
content: description,
},
{
name: 'og:title',
content: `${props.customProps.title}`,
},
],
})
<template>
<p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>
```
### [中間層 <middleware 目錄> ** 重要 **](https://v3.nuxtjs.org/guide/directory-structure/middleware)
> middleware翻譯為中介層,介於server端跟client,因此**可以在畫面渲染完前是先拿到server端的資料**,有點類似於`vue-router`的路由守衛,**但他能夠做更多事**
1.你可以用它來檢查權限,重定向,發api請求...都沒人攔你了
2. 命名規範如`components`為 kebab-case,例: someMiddleware => some-middleware)
```
-| middleware/
---| someMiddleware.ts
```
```javascript=
//範例 檢查params
export default defineNuxtRouteMiddleware((to, from) => {
if (to.params.id === '1') {
return abortNavigation()
}
return navigateTo('/')
})
//in page
<script setup>
definePageMeta({
middleware: ["some-middleware"]
// or middleware: 'some-middleware'
})
</script>
```
3. 全局路由中間件,位於 middleware/ 目錄(後綴為 .global),每次路由更改都會自動運行。
4. 參考官網[範例](https://v3.nuxtjs.org/examples/routing/middleware/)
### [自訂義layout <layouts 目錄>](https://v3.nuxtjs.org/guide/directory-structure/layouts#enabling-the-default-layout)
> 注意:佈局名稱被規範為 kebab-case,例如: someLayout => some-layout。
- 可以定義多個layout,並在`middleware`裡動態切換每一頁的layout,參考[setPageLayout ](https://v3.nuxtjs.org/api/composables/set-layout)
```
-| layouts/
---| default.vue
---| custom.vue
....etc
```
- 基本用法
```html=
//in layout/default.vue
<template>
<div id="default-layout" class="layout select-none">
<Navbar />
<main class="w-4/5">
<slot />
</main>
<footer class="bg-sky-300 flex-center">
footer
</footer>
</div>
</template>
```
```html=
//in app.vue
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
```
### [自訂義Hook <composables 目錄>](https://v3.nuxtjs.org/guide/directory-structure/components)
> Nuxt 會自動載入這個目錄中的任何元件。
1. Nuxt 3 會自動掃描 `.js`, `.ts` 與 `.vue` 副檔名的檔案,但只有**最上層的檔案,才會自動的被載入為組合式函數**
- ex: ./composables/useCounter.js
- ex: ./composables/time/index.js
3. 建議在建立組合式函數可以**使用 use 作為開頭來加以識別。**
### [自訂義組件 <components 目錄>](https://v3.nuxtjs.org/guide/directory-structure/components)
> Nuxt 會自動載入這個目錄中的任何元件
- 基礎組件
1. **名稱會以資料夾結構命名**,如下的話就會是`BaseFooButton`
```
| components/
--| base/
----| foo/
------| Button.vue
```
2. 若是像這樣就會是`Icon`
```
| components/
--| icon/
----| index.vue
```
- [只在客戶端渲染的組件(<ClientOnly> Component)](https://v3.nuxtjs.org/guide/directory-structure/components#clientonly-component)
1. 基本用法
```javascript=
<template>
<div>
<Sidebar />
<!-- This renders the "span" element on the server side -->
<ClientOnly fallbackTag="span">
<!-- this component will only be rendered on client side -->
<Comments />
<template #fallback>
<!-- this will be rendered on server side -->
<p>Loading comments...</p>
</template>
</ClientOnly>
</div>
</template>
```
2. 如果組件僅在客戶端呈現,則可以將 `.client` 後綴添加到組件中。(**註: 此功能++僅適用於 Nuxt 自動導入的組件++。手動導入的組件不會被轉換為僅限客戶端的組件。**)
```
| components/
--| Comments.client.vue
```
3. 只在serve端渲染的組件跟客戶端組件類似,將後綴改成`.server`即可
4. 交叉使用
```
| components/
--| Comments.client.vue
--| Comments.server.vue
```
```html=
<template>
<div>
會依照環境自動顯示不同的組件
<Comments />
</div>
</template>
```
### [自訂義插件 <plugins目錄>](https://v3.nuxtjs.org/guide/directory-structure/plugins/)
> Nuxt 會自動載入這個目錄檔案,作為插件使用,在檔案名稱可以使用後綴 .server 或 .client,例如, plugin.server.ts 或 plugin.client.ts 來決定只讓伺服器端或客戶端載入這個插件。
```javascript=
// in plugins/my-plugins.ts
export default defineNuxtPlugin(() => {
return {
provide: {
hello: (msg: string) => `Hello ${msg}!`
}
}
})
```
```javascript=
// in other page
const { $hello } = useNuxtApp()
console.log($hello('name')) //Hello name
```
### [自訂義api <server目錄>](https://v3.nuxtjs.org/guide/directory-structure/server)
> Nuxt會自動掃描~/server/api, ~/server/routes, and ~/server/middleware這幾個資料夾內的檔案並自動引入
- 注意: **為了能完美支援client端及server端**,`Nuxt3`不支持`Axios`,只可以使用`$fetch API`或者使用`Nuxt3`提供的原生api方法
- 在`Nuxt2`使用`Axios`時有些小狀況: 例如:在server端會拿到字串(json格式),部屬會噴錯,只好手動判斷如果是`server`端就先轉成物件
- `Nuxt3`使用了[ohmyfetch](https://github.com/unjs/ohmyfetch)作為預設(用起來跟`Axios`差不多)
)
- 可以定義api後直接本地訪問`api/yourAPIName`
- 詳細可參考[此篇](https://ithelp.ithome.com.tw/articles/10301197)
#### 先使用`ohmyfetch`create一個apiFetch
```javascript=
// in server/api/index.ts
import { $fetch } from 'ohmyfetch' // 記得要先下載`ohmyfetch`,不然會拿不到$featch
const apiFetch = $fetch.create({
baseURL: '/api',
headers: {
'Accept': 'application/json',
'Cache-Control': 'no-cache',
},
})
export { apiFetch }
```
#### [定義一個api](https://v3.nuxtjs.org/guide/directory-structure/server/)
```javascript=
// in server/api/greeting.ts
const data = (name: unknown) => {
return {
code: 200,
data: `Hello ${name}`,
}
}
export default defineEventHandler(async (event) => {
const { name } = getQuery(event) // 解析查詢參數
return new Promise((resolve, reject?: unknown) => {
setTimeout(() => {
resolve(data(name))
}, 1000)
})
})
```
- 備註: **這種用法比較適合一頁需要打數隻API的情形**,一頁只要一支的話推薦用`Nuxt3`原生提供的`useFeatch`方法,畢竟`pending`, `error`, `refresh`都幫你封裝好了
```javascript=
// in pages/index.vue
import { apiFetch } from '~/server/api'
const getNameAndGreeting = async () => {
const res = await apiFetch('greeting', {
params: { name: 'Name' },
})
console.log(res) // Hello Name
}
onMounted(async () => {
await getNameAndGreeting()
})
```
### [Nuxt提供的原生Hook](https://v3.nuxtjs.org/api/composables/use-state/)
> 詳解也可參考這篇[[Day 15] Nuxt 3 資料獲取 (Data Fetching)](https://ithelp.ithome.com.tw/articles/10301876)
1. Nuxt3中提供的數據獲取函數有以下四個:
> 注意:它們都必須在setup或生命週期鉤子中使用,回傳值如下
`data`: 傳入異步函數的回傳結果。
`pending`: 以 `true` 或 `false` 表示是否正在獲取資料。
`refresh / execute`: 一個函數,可以用來重新執行 `handler` 函數,回傳新的資料,類似重新整理、重打一次 API 的概念。預設情況下 `refresh()` 執行完並回傳後才能再次執行。
`error`: 資料獲取失敗時回傳的物件。
- useFetch
- useFetch是對useAsyncData包裝,自動生成key同時推斷響應類型,用起來更簡單。
- useLazyFetch (不會阻擋路由)
- useAsyncData
- 異步資料獲取
- useLazyAsyncData (不會阻擋路由)
2. 獲取/存取狀態有以下兩種
- [useState](https://ithelp.ithome.com.tw/articles/10302323)
- 類似pinia
- [useCookie](https://ithelp.ithome.com.tw/articles/10304667)
- 方便讀寫cookie
### [環境變數 <env 目錄>](https://cn.vitejs.dev/guide/env-and-mode.html#env-files)
- client端環境變數
由於`Nuxt3`是基於`vite`製作的,拿法基本上一樣
1. 創建`env`資料夾,裡面新增`.env.development`及`.env.production`
2. 在`env`資料夾裡創建`env.d.ts`
3. 在`nuxt.config.js`裡的`vite`新增`envDir: 你的env資料夾位置`
4. **注意**: 在`webpack`或`vite`定義環境變數的時候字串要加引號,`Nuxt`不用,只要在`env.d.ts`裡面定義好就好了,**否則會拿不到**
5. 範例
```javascript=
// in .env.development
VITE_PROJECT_ENV = 'deve'
VITE_APP_TITLE = MY NUXT3 PROJECT
VITE_API_URL = /api
VITE_SOME_KEY = 123
```
```javascript=
// in env.d.ts
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
VITE_API_URL: string
VITE_SOME_KEY: number
// more env variables...
}
```
```javascript=
// in nuxt.config.ts
import { resolve } from 'path'
function pathResolve(dir: string) {
return resolve(__dirname, dir)
}
export default defineNuxtConfig({
...略
vite: {
envDir: pathResolve('./src/env'),
},
})
```
```javascript=
// in index.vue
console.log(import.meta.env) // 結果如下圖
```
![](https://i.imgur.com/3GPbEQ6.png)
- [server端環境變數請參考這篇](https://ithelp.ithome.com.tw/articles/10303583)
> 只需記得,不能公開的金鑰或敏感訊息,會放置在`runtimeConfig`中的在app屬性內
### [其他重要檔案](https://ithelp.ithome.com.tw/articles/10295432)
- .nuxtignore
> 可以設置讓 Nuxt 編譯建構時,一些不需要或忽略檔案。
- app.config.ts
> 提供服務運行時暴露給客戶端使用的設定,因此,**請不要在 app.config.ts 檔案中添加任何機密資訊。**
### 參考文章
- [A starter example with Nuxt3 + Windi CSS + Iconify + Element-plus + Pinia + Docker](https://bestofvue.com/repo/xbmlz-nuxt-starter)
- [nuxt3配置多环境变量](https://yezipi.net/article/detail/10097)
- [nuxt3 Element-plus 自动导入](https://juejin.cn/post/7108916330153115655)
- [【Nuxt3从入门到实战】巧用Nuxt插件机制,扩展强化Nuxt的利器!](https://www.51cto.com/article/693625.html)
- [不只懂 Vue 語法:以 Vue 和 Nuxt 為例,說明 SPA 和 SSR 的概念?](https://github.com/xiaoluoboding/nuxt3-starter)
- [19. Nuxt Middleware,請求與回應間的中盤商](https://ithelp.ithome.com.tw/articles/10207822)
- [dotenvはもう古い!最新Nuxtの環境変数管理方法](https://zenn.dev/tai_hatake/articles/c0d754bb7ae230)
- [历时两个月!Nuxt3从入门到实战!你值得收藏!](https://juejin.cn/post/7037336504418435103)
- [Ryan (ryanchien8125) Nuxt 3 學習筆記 系列全篇(推薦閱讀,很詳細)](https://ithelp.ithome.com.tw/users/20152617/ironman/5934)
- [Day03 - 深入淺出 CSR、SSR 與 SSG](https://ithelp.ithome.com.tw/articles/10266781)
- [Nuxt 3 特色功能(二)网页头部](https://juejin.cn/post/7103696957327015943)
### 網上大神的開源模板
- [oku-nuxt3-template 使用tailwindcss](https://github.com/productdevbook/oku-nuxt3-template)
- [nuxt3-starter](https://ithelp.ithome.com.tw/articles/10262891)