# 🏅 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 和客戶端的渲染中。 ![day12-difference](https://hackmd.io/_uploads/HJa5r5YMkx.jpg) - useServerSeoMeta 只會在伺服器端渲染,不會在客戶端進行渲染。伺服器端渲染完成後,`useServerSeoMeta` 的 Meta 標籤不會在客戶端渲染,減少了客戶端渲染時的額外開銷,適合僅需提供搜尋引擎爬蟲抓取,不需要在客戶端進行展示或更新的 Meta 資訊。例如下圖,在 `/pages/about.vue` 中使用 `useServerSeoMeta` ,伺服器端會顯示 `useServerSeoMeta` 的 SEO Meta,而客戶端只會顯示 `useSeoMeta` 的 SEO Meta。 ![CleanShot 2024-11-19 at 12.36.12@2x](https://hackmd.io/_uploads/rJZkI5FGJe.jpg) <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://i.imgur.com/vftL5i0.png) <!-- 解答 : 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]() | -->