# 🏅 Day 12 - useSeoMeta 與 useServerSeoMeta
## 今日學習目標
- 使用 useSeoMeta 與 useServerSeoMeta Composable 設定 Meta 資訊
- 理解 useSeoMeta 與 useServerSeoMeta 的差異
## 前言
Day 11 的練習中,我們在 /pages/index.vue 使用了 `useHead()` 來設定頁面的 Meta 資訊,包括 `title`、`og:title`、`og:image`、`og:description` 及 `description`。儘管 `useHead()` 可以設定 Meta 資訊,但它需要精確定義每個 Meta 標籤的屬性名稱(如 `property`、`name` 和 `content`)。一旦屬性名稱或結構撰寫有誤,將會增加撰寫的複雜度,造成不必要的除錯時間。
Nuxt 不會自動檢查這些屬性名稱的正確性,如果定義錯誤將會渲染出不正確的 Meta 資訊。常見的錯誤之一是錯誤地使用 `name` 和 `property` 屬性,例如 `og:` 系列 Meta 使用的 `name` 屬性,正確為 `property` 而非 `name` 。這樣的錯誤會導致渲染結果變成 `<meta name="…" content="…">` ,造成 Open Graph 資訊無法正確被解析,影響網頁在社群平台的預覽效果。
以下為錯誤示範:
```html
<!-- /pages/index.vue -->
<script setup>
const title = ref("首頁 - Nuxt3 Day11 useHead() 練習");
useHead({
title,
meta: [
{
name: "og:title", // 屬性錯誤,正確應為 property: "og:title",
content: "首頁 - Nuxt3 Day11 useHead() 練習",
},
{
name: "og:image", // 屬性錯誤,正確應為 property: "og:image",
content: "http://localhost:3000/share.jpg" },
{
name: "og:description", // 屬性錯誤,正確應為 property: "og:description",
content: "首頁 - 透過今天的學習,將會學習到 Nuxt3 useHead() 的使用方法 ",
},
{
name: "description",
content: "首頁 - 透過今天的學習,將會學習到 Nuxt3 useHead() 的使用方法 ",
},
],
});
</script>
```
## useSeoMeta 與 useServerSeoMeta
為了解決屬性寫錯與結構複雜的問題,Nuxt3 提供了組合式函式 ( Composable ) `useSeoMeta` 與 `useServerSeoMeta`,允許我們使用結構簡單的物件處理 Meta 標籤 ,不需要手動撰寫多層級的物件陣列(如 `meta` 陣列),也不需要記憶正確的屬性名稱(如 `name` 和 `property`)。
### 撰寫方式
`useSeoMeta` 與 `useServerSeoMeta` 的參數由 [Unhead](https://unhead.harlanzw.com/) 套件支援。我們可以依據 [metaFlat.ts](https://github.com/harlan-zw/zhead/blob/main/packages/zhead/src/metaFlat.ts) 的 Schema 進行撰寫。舉例來說, `<meta property="og:title" content="首頁 - Nuxt3 Day11 useHead() 練習">` 在 `useSeoMeta({})` 中,可以使用小駝峰命名的 `ogTitle` 屬性,如下面的範例所示:
```jsx
useSeoMeta({
ogTitle: "首頁 - Nuxt3 Day11 useHead() 練習",
});
useServerSeoMeta({
ogTitle: "首頁 - Nuxt3 Day11 useHead() 練習",
});
```
這個範例中省略了 `property` 與 `content` 屬性的撰寫,直接以 key-value 的形式設定 Meta 資訊,將 `property` 屬性的值(如 `og:title`)作為 key,而 `content` 屬性的值則是對應到 value。
### 範例練習與 meta 屬性反推
接下來我們練習把 Day 11 中 `/pages/index.vue` 的 `useHead()` 寫法轉換為 `useSeoMeta` 與 `useServerSeoMeta` ,如下 :
```html
<!-- /pages/index.vue -->
<script setup>
useSeoMeta({
ogTitle: '首頁 - Nuxt3 Day12 useSeoMeta() 與 useServerSeoMeta() 練習',
ogImage: 'http://localhost:3000/share.jpg',
ogDescription: '首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法',
description: '首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法',
});
useServerSeoMeta({
ogTitle: '首頁 - Nuxt3 Day12 useSeoMeta() 與 useServerSeoMeta() 練習',
ogImage: 'http://localhost:3000/share.jpg',
ogDescription: '首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法',
description: '首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法',
});
</script>
```
上面的練習使用了 [metaFlat.ts](https://github.com/harlan-zw/zhead/blob/main/packages/zhead/src/metaFlat.ts) 規則的 `ogTitle`、`ogImage`、`ogDescription` 和 `description` 屬性來設定 SEO 資訊。這些屬性可以反推成以下的 HTML 渲染結果 :
```jsx
// property 屬性的值 "og:title" 轉換成小駝峰命名的 ogTitle 為物件的 key ,content 屬性的值對應到 ogTitle 屬性的值
<meta property="og:title" content="首頁 - Nuxt3 Day12 useSeoMeta() 與 useServerSeoMeta() 練習">
// property 屬性的值 "og:image" 轉換成小駝峰命名的 ogImage 為物件的 key ,content 屬性的值對應到 ogImage 屬性的值
<meta property="og:image" content="http://localhost:3000/share.jpg">
// property 屬性的值 "og:description" 轉換成小駝峰命名的 ogDescription 為物件的 key ,content 屬性的值對應到 ogDescription 屬性的值
<meta property="og:description" content="首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法">
// name 屬性的值 "description" 為物件的 key ,content 屬性的值對應到 description 屬性的值
<meta name="description" content="首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法">
```
如果需要添加更多 Meta 標籤,例如 Twitter 卡片的 Meta 資訊,也可以使用相同的 key-value 方式來定義:
```jsx
useSeoMeta({
twitterCard: "summary_large_image",
twitterTitle: "twitter 卡片標題",
twitterDescription: "twitter 卡片描述",
twitterImage: "http://localhost:3000/share.jpg" ,
});
useServerSeoMeta({
twitterCard: "summary_large_image",
twitterTitle: "twitter 卡片標題",
twitterDescription: "twitter 卡片描述",
twitterImage: "http://localhost:3000/share.jpg" ,
});
// 反推的 HTML 渲染結果:
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="twitter 卡片標題">
<meta name="twitter:description" content="twitter 卡片描述">
<meta name="twitter:image" content="http://localhost:3000/share.jpg">
```
## 傳入具響應性的資料
`useSeoMeta` 和 `useServerSeoMeta` 支援使用 `ref` 和 getter 語法來設定響應性的 SEO meta 資訊。當資料狀態變化時,這些方法會自動更新相應的 SEO meta 標籤。以下是使用 `ref` 和 getter 語法的範例及說明。
### 使用 ref 設定 SEO Meta 資訊
在這個範例中,`ogTitle` 被設定為響應性的 `ref`。當 `ogTitle` 的值在伺服器端環境中發生變化,`useSeoMeta` 和 `useServerSeoMeta` 會自動更新 `<meta property="og:title">` 的內容。
```html
<!-- /pages/index.vue -->
<script setup>
const ogTitle= ref("首頁 - Nuxt3 Day12 useSeoMeta() 與 useServerSeoMeta() 練習");
useSeoMeta({
title: "首頁",
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: "首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法",
description: "首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法",
});
useServerSeoMeta({
title: "首頁",
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: "首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法",
description: "首頁 - 透過今天的學習,將會學習到 Nuxt3 useSeoMeta() 與 useServerSeoMeta() 的使用方法",
});
</script>
```
### 使用 Getter 函式設定 SEO Meta 資訊
若希望根據使用者操作或是 API 請求動態改變 SEO meta 資訊,可以使用 getter 語法 ( **`() => value`** ) 動態計算屬性值。這樣可以確保資料變動時,相關的 meta 資訊能夠自動更新。
SEO meta 資訊經常需要跟隨資料更新動態變化。例如,頁面的標題和學習內容會隨資料變動,可以把 “useSeoMeta() 與 useServerSeoMeta()” 提取成 `theme` 變數,"首頁" 提取成 `title` 變數,並使用 getter 語法為 `ogTitle`、`description`、`ogDescription` 屬性設定動態值。 當 `title` 和 `theme` 變數的值在伺服器端環境變動時將會觸發 getter 更新 SEO Meta 資訊。
```html
<!-- /pages/index.vue -->
<script setup>
const title = ref("首頁");
const theme = ref("useSeoMeta() 與 useServerSeoMeta()");
const ogTitle = () => `${title.value} - Nuxt3 Day12 ${theme.value} 練習`;
useSeoMeta({
title: () => `${title.value} - Nuxt3 Day12 ${theme.value} 練習`,
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
description: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
});
useServerSeoMeta({
title: () => `${title.value} - Nuxt3 Day12 ${theme.value} 練習`,
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
description: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
});
</script>
```
## useSeoMeta 與 useServerSeoMeta 的侷限性
`useSeoMeta` 和 `useServerSeoMeta` 基於 [metaFlat.ts](https://github.com/harlan-zw/zhead/blob/main/packages/zhead/src/metaFlat.ts) 的 Schema 規範,主要用來設定 SEO 相關的 Meta 標籤。常見且支援的屬性包括: `title`、`description` 、`ogTitle`、`ogDescription`、`ogImage` 和 `keywords`。
然而,像 `titleTemplate`、`link`、`style`、`script`、`noscript`、`bodyAttrs` 和 `htmlAttrs` 這些屬性不包含在 `metaFlat.ts` 的規範內,仍需透過 `useHead` 來管理。因此,當專案需要使用這些屬性時,必需將 `useHead` 與 `useSeoMeta` 或 `useServerSeoMeta` 結合使用。以下範例將使用 `titleTemplate` 屬性來進行說明。
### titleTemplate 與動態標題
`titleTemplate` 屬性用於建立動態標題模板。當 `title` 屬性的值改變時,`titleTemplate`
會自動套用新值來更新 `<title>` 標籤。關於 `titleTemplate` 可以閱讀 [官方文件](https://nuxt.com/docs/getting-started/seo-meta#title-template) 的說明。
以 `useSeoMeta` 和 `useServerSeoMeta` 的 `title` 屬性為例進行改寫,結果如下。若 `titleTemplate` 模板需要加入動態更新的值,需要使用 getter 語法,將 `title` 和 `theme` 變數傳入 `titleTemplate` 的函式中。
```html
<!-- /pages/index.vue -->
<script setup>
const title = ref("首頁");
const theme = ref("useSeoMeta() 與 useServerSeoMeta()");
const ogTitle = () => `${title.value} - Nuxt3 Day12 ${theme.value} 練習`;
useHead({
titleTemplate: (title) => `${title} - Nuxt3 Day12 ${theme.value} 練習`,
});
useSeoMeta({
title,
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
description: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
});
useServerSeoMeta({
title,
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
description: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
});
</script>
```
### 統一管理 title 屬性
如果希望在 `useHead` 集中管理 `title` 屬性,可以將 `title` 屬性移到 `useHead` 中。
```html
<!-- /pages/index.vue -->
<script setup>
const title = ref("首頁");
const theme = ref("useSeoMeta() 與 useServerSeoMeta()");
const ogTitle = () => `${title.value} - Nuxt3 Day12 ${theme.value} 練習`;
useHead({
title,
titleTemplate: (title) => `${title} - Nuxt3 Day12 ${theme.value} 練習`,
});
useSeoMeta({
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
description: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
});
useServerSeoMeta({
ogTitle,
ogImage: "http://localhost:3000/share.jpg",
ogDescription: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
description: () =>
`${title.value} - 透過今天的學習,將會學習到 Nuxt3 ${theme.value} 的使用方法`,
});
</script>
```
## useSeoMeta 與 useServerSeoMeta 的差異
`useSeoMeta` 和 `useServerSeoMeta` 都是用來設定 SEO 相關的 Meta 標籤,兩者的差異在於 :
- `useSeoMeta` 在伺服器端與客戶端都渲染相同的 Meta 標籤,並且可以根據響應式資料(如 `ref` 或 `computed`)在客戶端動態更新頁面的 Meta 標籤。例如下圖,在 `/pages/about.vue` 中使用 `useSeoMeta`,會發現伺服器端(透過右鍵 "檢視網頁原始碼" 檢視)和客戶端(透過瀏覽器的開發工具檢視)渲染的 Meta 標籤是相同的。這是因為 `useSeoMeta` 的 Meta 標籤會被包含在伺服器端渲染的 HTML 和客戶端的渲染中。

- useServerSeoMeta 只會在伺服器端渲染,不會在客戶端進行渲染。伺服器端渲染完成後,`useServerSeoMeta` 的 Meta 標籤不會在客戶端渲染,減少了客戶端渲染時的額外開銷,適合僅需提供搜尋引擎爬蟲抓取,不需要在客戶端進行展示或更新的 Meta 資訊。例如下圖,在 `/pages/about.vue` 中使用 `useServerSeoMeta` ,伺服器端會顯示 `useServerSeoMeta` 的 SEO Meta,而客戶端只會顯示 `useSeoMeta` 的 SEO Meta。

<br>
> 今日學習的[範例 Code - 資料夾: day12-useseometa-seo](https://github.com/hexschool/nuxt-daily-tasks-2024)
## 題目
請 fork 這一份 [模板](https://github.com/jasonlu0525/nuxt3-live-question/tree/day12-useseometa-seo) 在 `/pages/room/[id].vue` 房型詳細頁面作答,完成以下條件 :
- 在取得房型詳細資料的 `roomObject` 物件後,使用 `useSeoMeta` 將 `roomObject` 的資訊寫入 SEO Meta 。
- 伺服器端提交給搜尋引擎爬蟲以及客戶端渲染的 SEO Meta 皆使用使用下方結構的標籤。請撰寫 useSeoMeta({ }) 渲染出下方的 HTML 結構,並將 `{{ }}` 替換成使用 roomObject 物件的資料。
## 回報流程
將答案上傳至 GitHub 並複製 GitHub repo 連結貼至底下回報就算完成了喔 !
解答位置請參考下圖(需打開程式碼的部分觀看)

<!--
解答 : https://github.com/jasonlu0525/nuxt3-live-answer/tree/day12-useseometa-seo
-->
回報區
---
| # | Discord | Github / 答案 |
| --- | ----- | ----- |
|1|眼睛|[Github](https://github.com/Thrizzacode/nuxt3-live-question/tree/day12-useseometa-seo)|
|2|kevinhes|[Github](https://github.com/kevinhes/nuxt-daily-mission/tree/day12)|
|3|LinaChen|[Github](https://github.com/Lina-SHU/nuxt3-live-question)|
| 4 | Steven | [Github](https://github.com/y7516552/nuxt3-live-question/tree/day12) |
| 5 | dragon | [Github](https://github.com/peterlife0617/2024-nuxt-training-homework01/tree/feature/day12) |
| 6 | MY | [Github](https://github.com/ahmomoz/nuxt3-live-question/tree/day12-useseometa-seo-hw) |
| 7 | Jim Lin | [Github](https://github.com/junhoulin/Nuxt3-hw-day-after10/tree/day12) |
| 8 | wei_Rio | [Github](https://github.com/wei-1539/nuxtDaily10/tree/Day-12---useSeoMeta-%26-useServerSeoMeta)|
| 9 | Rocky | [Github](https://github.com/WuRocky/Nuxt-Day12-useSeoMeta-useServerSeoMeta.git) |
| 10 |hsin yu | [Github](https://github.com/dogwantfly/nuxt3-daily-task-live-question/tree/day12-useseometa-seo) |
| 11 | 阿塔 | [Github](https://github.com/QuantumParrot/2024-Hexschool-Nuxt-Camp-Daily-Task/blob/8504a7b45cda4404ce9547f01b091eeb6ef54ea5/pages/room/%5Bid%5D.vue#L67) |
| 12 | Tough life | [Github](https://github.com/hakuei0115/Nuxt_testing) |
| 13 | tanuki狸 | [Github](https://github.com/tanukili/Nuxt-2024-week01-2/tree/day12-useseometa-seo) |
| 14 | Fabio20 | [Github](https://github.com/fabio7621/nuxt3-live-question-d2/tree/nuxt-live-day12) |
| 15 | Ariel | [Github](https://github.com/Ariel0508/nuxtday9/tree/nuxtDay12) |
| 16 | lidelin | [Github](https://github.com/Lide/nuxt3-live-question/tree/day12-useseometa-seo) |
| 17 | Johnson | [Github](https://github.com/tttom3669/2024_hex_nuxt_daily/tree/day12-useseometa-seo) |
<!--
快速複製
| --- | --- | [Github]() |
-->