Skip to main content

Nuxt3 概觀

Nuxt 環境基本介紹

在初始 Nuxt 專案下會發現環境好像異常的空,比之前用 vite 建立專案少了一些資料夾,比如說 components 之類的。這不代表 Nuxt 沒有,但它選擇並不幫你預設建立,而是讓我們自己按照需求新增。
可以新增給 Nuxt 做判讀的資料夾可以參照官網

以下為幾個專案很常用到的資料夾:

  1. components:存放元件的地方。
  2. pages:存放頁面的地方。
  3. utils:把常用的功能寫成一個函數共用儲存的地方。
  4. assets:用來存放需要打包的靜態檔案,諸如各式各樣 CSS、SCSS 或字型。
  5. public:用來存放那些不需要打包的靜態檔案,比如 favicon。
  6. middleware:路由中間件放置處,比如跳轉頁面時要先運行的身分驗證。

nuxt.config.ts

這是 Nuxt 的設定檔,諸多功能或套件的配置都要在這裡進行。
以 pinia 舉例,在這裡就是設定 Nuxt 使用 pinia 並在每個檔案自動引入 defineStoredefinePiniaStore 兩個方法。

export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
[
"@pinia/nuxt",
{
autoImports: ["defineStore", ["defineStore", "definePiniaStore"]],
},
],
],
});
tip

對於一些隱私設定,比如一些不想讓外人知道的 api 路徑,可以使用 runtimeConfig 做限制隱藏。

export default defineNuxtConfig({
runtimeConfig: {
// 只在伺服器可見
apiSecret: '/api/private',
// 伺服器或瀏覽器皆可見
public: {
apiBase: '/api'
}
}
})

Nuxt 的自動路由

凡是放在 pages 的檔案,都會自動被 Nuxt 做成路由,這是很多人選擇直接使用 Nuxt 開發的原因,因為可以減去很多編輯 vue router 的繁瑣工作。
在 pages 中,通常會有一個 index.vue,他對應的路由就是 /。其它檔案的路由就依其名字而命名,比方說 about.vue 的路由會是 /about

這裡注意一點,不是在 pages 建立了檔案 Nuxt 就會做渲染,必須先在 app.vue 中做引入才能正確渲染:

<template>
<div>
<NuxtPage />
</div>
</template>

動態路由

在 Nuxt 玩動態路由只要把檔名用 [ ] 包起來即可,如 [id].vue

官方範例
| pages/
---| about.vue
---| index.vue
---| posts/
-----| [id].vue

template 中只要使用 $route.params.id 即可取得動態路由資訊,而在 <script setup> 中要先引用 useRoute

template
<template>
<h1>page: {{ $route.params.id }}</h1>
</template>
<script setup
const route = useRoute()

console.log(route.params.id)

巢狀路由

簡單來說就是用資料夾一個套住一個,以官方範例來看,下列ru,5夠會產出 /parent/parent/child 兩個路由。

-| pages/
---| parent/
------| child.vue
---| parent.vue

路由匹配

先看 vue router 的路由匹配:

{
path: '/about/:afterAbout',
component: AboutView
},
{
path: '/about/:afterAbout(\\d+)',
component: NotFound
}

在 Nuxt 中則稍稍麻煩一點,需要使用 definePageMeta 中的 validate 來做匹配,假設我有個結構如下:

資料夾結構
-| pages/
---| book/
------| [id].vue
---| book.vue

我要當 id 為數字時進到這一頁 (/book/id),不然就連回 /book

<script setup>
const route = useRoute()

definePageMeta({
validate: async (route) => {
// 檢查id是否由數字組成
if(/^\d+$/.test(route.params.id)){
return true
}else{
return {path: `/books`}
}
}
})
</script>

<template>
<h1>{{ route.params.id }}</h1>
</template>

layouts

會用到 layout 的情況就是複數頁面有共用的組件或排版樣式,比如說導覽列。
layouts 資料夾中,命名為 default.vue 的會是預設為全局 layout,只要在 app.vue 中給予 <NuxtLayout> 設定,就完成了一半的 layout 引用:

app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

然後必須在 layout 中,在想要套入其他頁面或元件的地方加一個 <slot/>

default.vue
<template>
<div>
<slot />
</div>
</template>

而如果今天有某特定頁面想引用一個特定的 layout,比如說登入、註冊頁面想套用同一個 layout (比如 sign.vue) 而不是 default.vue,我們可以在該頁面 <script setup> 中進行頁面設定:

<script setup lang="ts">
definePageMeta({
layout: 'sign'
})
</script>

設定網站標頭

  1. 先到 nuxt.config.ts 中設定下列內容:
export default defineNuxtConfig({
app: {
head: {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
}
}
})
  1. app.vue,添加下面這這一段:
<script setup lang="ts">
useHead({
title: '這裡是title',
meta: [
{ name: 'description', content: '這是測試用網站' }
],
bodyAttrs: {
class: 'test'
},
script: [ { innerHTML: 'console.log(`Hello world`)' } ]
})
</script>

轉場效果

  1. 先到 nuxt.config.ts 中設定下列內容:
export default defineNuxtConfig({
app: {
pageTransition: { name: 'page', mode: 'out-in' }
}
})
  1. app.vue,添加下面這這一段:
<style>
.page-enter-active,
.page-leave-active {
transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
filter: blur(1rem);
}
</style>

也可以僅針對一個頁面做轉場,比如進到 about 頁面才會有轉場效果,這時必須先在 /page/about.vue 中做設定:

<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'rotate'
}
})
</script>

然後回 app.vue 添加樣式,記得使用剛剛定義的 name

<style>
.rotate-enter-active,
.rotate-leave-active {
transition: all 0.4s;
}
.rotate-enter-from,
.rotate-leave-to {
opacity: 0;
transform: rotate3d(1, 1, 1, 15deg);
}
</style>

靜態網頁部署

  1. 按裝 gh-pages
npm i -D gh-pages
  1. 修改 nuxt.config.ts
export default defineNuxtConfig({
app: {
baseURL: process.env.NODE_ENV === 'production' ? '/your-github-repo/' : '/',
buildAssetsDir: '/static/'
}
})
  1. 在根目錄的 public 新增 .nojekyll 檔案。
  2. package.json 中新增部署指令:
"deploy": "gh-pages --dotfiles -d .output/public"
  1. 部署:
npm run generate
npm run deploy

Vercel 部署

建立 Vercel 帳號

warning

這裡默認已經有 GitHub 帳號並把之前的練習上傳到 GitHub repo 上了。

Vercel 選擇 hobby 免費方案,使用 GitHub 登入即可。

Vercel 部署

辦完 Vercel 帳號後可以直接連通 GitHub repo,import 專案後可以直接一鍵部署。

建立 Postgres database

建立與連接資料庫

  1. 部署完成後,點選 Continue to Dashboard,選擇 Storage,接著前往下列路徑 Postgres → Create。當創建完成後,點擊 Connect 連結資料庫。

  2. 接著點擊前往 .env.local,點擊 Show secretCopy Snippet

  3. 回到本地專案中,在 .env 貼上剛剛複製的內容。

warning

確保 .env 檔案有被加入 .gitignore 中。

  1. 安裝 Vercel Postgres SDK:
pnpm install @vercel/postgres
  1. 安裝 dotenv 來讀取環境變數:
pnpm install dotenv

建立種子資料

warning

以下為個人實作專案範例,部分不重要內容會省略。

  1. 建立 /scripts/seed.js,裡面已經有 Next 官方製作好用來產生種子資料的指令。
const { db } = require('@vercel/postgres')

async function clearTables(client) {
try {
// 清空所有資料表
await client.query(`
DROP TABLE IF EXISTS order_details;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS users;
`)
console.log('Tables cleared successfully.')
} catch (error) {
console.error('Error clearing tables:', error)
throw error
}
}

async function createTables(client) {
try {
// 創建用戶表
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
address VARCHAR(255) NOT NULL,
phone VARCHAR(255) NOT NULL,
email VARCHAR(255),
payment VARCHAR(255) NOT NULL
);
`)

// Details Skipped

console.log('Tables created successfully.')
} catch (error) {
console.error('Error creating tables:', error)
throw error
}
}

// 在 main 函數中連接數據庫,並調用 createTables 函數創建表
async function main() {
const client = await db.connect()
await clearTables(client)
await createTables(client)
await client.end()
}

main().catch((err) => {
console.error('An error occurred while creating tables:', err)
})
  1. 接下來前往 package.json 增加一段指令:
"scripts": {
"build": "next build",
"dev": "next dev",
"start": "next start",
"seed": "node -r dotenv/config ./scripts/seed.js"
},
  1. 執行指令後回到 Vercel,在側欄找到 Data,就可以瀏覽剛剛建立的表格了。

參考資料

  1. Nuxt.js 3.x 將靜態網站部署到 GitHub Pages